From 79d8b95f4ef46200a8eb14ed1b470d017786df7b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 27 Sep 2020 00:43:17 +1000 Subject: [PATCH 01/74] bug: actually use the absolute path we generate for crashpad Fixes #1230 --- app/common/crashpadinterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/common/crashpadinterface.cpp b/app/common/crashpadinterface.cpp index c65db16d0..adc954e49 100644 --- a/app/common/crashpadinterface.cpp +++ b/app/common/crashpadinterface.cpp @@ -86,7 +86,7 @@ bool InitializeCrashpad() bool status = false; if (QFileInfo::exists(handler_abs_path)) { - base::FilePath handler(QSTRING_TO_BASE_STRING(handler_fn)); + base::FilePath handler(QSTRING_TO_BASE_STRING(handler_abs_path)); base::FilePath reports_dir = GenerateReportPathForCrashpad(); From a82e2d5ae52dc777e4ebb651ea76228b1ec10de0 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 28 Sep 2020 04:53:17 +1000 Subject: [PATCH 02/74] use cache location instead of temp location for ocio extraction Workaround for Linux not having a user-specific temp directory, causing potential permission issues if more than one user is accessing the extraction. Fixes #1239. --- app/common/filefunctions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/common/filefunctions.cpp b/app/common/filefunctions.cpp index f087dd90b..e137de3e8 100644 --- a/app/common/filefunctions.cpp +++ b/app/common/filefunctions.cpp @@ -73,7 +73,7 @@ QString FileFunctions::GetApplicationPath() QString FileFunctions::GetTempFilePath() { - QString temp_path = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)) + QString temp_path = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) .filePath(QCoreApplication::organizationName())) .filePath(QCoreApplication::applicationName()); From 909dd37cc2464b6a4a5c31edd802a4c56e25552c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 28 Sep 2020 14:20:48 +1000 Subject: [PATCH 03/74] files: continue using temp location for temp files and use cache location just for ocio --- app/common/filefunctions.cpp | 2 +- app/render/colormanager.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/common/filefunctions.cpp b/app/common/filefunctions.cpp index e137de3e8..f087dd90b 100644 --- a/app/common/filefunctions.cpp +++ b/app/common/filefunctions.cpp @@ -73,7 +73,7 @@ QString FileFunctions::GetApplicationPath() QString FileFunctions::GetTempFilePath() { - QString temp_path = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) + QString temp_path = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)) .filePath(QCoreApplication::organizationName())) .filePath(QCoreApplication::applicationName()); diff --git a/app/render/colormanager.cpp b/app/render/colormanager.cpp index 7c713edd6..2e4f496dd 100644 --- a/app/render/colormanager.cpp +++ b/app/render/colormanager.cpp @@ -22,6 +22,7 @@ #include #include +#include #include "common/define.h" #include "common/filefunctions.h" @@ -82,7 +83,7 @@ void ColorManager::SetUpDefaultConfig() } // Extract OCIO config - kind of hacky, but it'll work - QString dir = QDir(FileFunctions::GetTempFilePath()).filePath(QStringLiteral("ocioconf")); + QString dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).filePath(QStringLiteral("ocioconf")); FileFunctions::CopyDirectory(QStringLiteral(":/ocioconf"), dir, From a313fb525c5df38534ed69cd9f7c8bf6ab9803c5 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 28 Sep 2020 14:21:17 +1000 Subject: [PATCH 04/74] use QGraphicsView::FullViewportUpdate on TimelineViewBase Since this appears to be necessary for all derivatives anyway, we'll add this directly to the base class. --- app/widget/timelinewidget/view/timelineview.cpp | 1 - app/widget/timelinewidget/view/timelineviewbase.cpp | 13 +++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 99fd892a0..4b60ec0b7 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -46,7 +46,6 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) : setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setBackgroundRole(QPalette::Window); setContextMenuPolicy(Qt::CustomContextMenu); - setViewportUpdateMode(QGraphicsView::FullViewportUpdate); viewport()->setMouseTracking(true); } diff --git a/app/widget/timelinewidget/view/timelineviewbase.cpp b/app/widget/timelinewidget/view/timelineviewbase.cpp index 68acb08c7..b42978e26 100644 --- a/app/widget/timelinewidget/view/timelineviewbase.cpp +++ b/app/widget/timelinewidget/view/timelineviewbase.cpp @@ -44,16 +44,25 @@ TimelineViewBase::TimelineViewBase(QWidget *parent) : y_axis_enabled_(false), y_scale_(1.0) { + // Sets scene to our scene setScene(&scene_); - // Set default scale + // Set default scale (ensures non-zero scale from beginning) SetScale(1.0); + // Default to no default drag mode SetDefaultDragMode(NoDrag); - connect(&scene_, SIGNAL(changed(const QList&)), this, SLOT(UpdateSceneRect())); + // Signal to update bounding rect when the scene changes + connect(&scene_, &QGraphicsScene::changed, this, &TimelineViewBase::UpdateSceneRect); + // Always enforce maximum scale SetMaximumScale(kMaximumScale); + + // Workaround for Qt drawing issues with the default MinimalViewportUpdate. While this might be + // slower (Qt documentation says it may actually be faster in some situations), + // MinimalViewportUpdate causes all sorts of graphical crud building up in the scene + setViewportUpdateMode(QGraphicsView::FullViewportUpdate); } void TimelineViewBase::TimebaseChangedEvent(const rational &) From dac68cabc2991bdb5f2a21eae8bd83ceef60f60b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 28 Sep 2020 14:28:56 +1000 Subject: [PATCH 05/74] timeline: made ReplaceBlocksWithGaps more accessible for other parts of the program --- app/widget/timelinewidget/timelinewidget.cpp | 5 ----- app/widget/timelinewidget/timelinewidget.h | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 12d7bc0c4..f8b9cb73d 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -505,16 +505,11 @@ void TimelineWidget::DeleteSelected(bool ripple) { QList selected_list = GetSelectedBlocks(); QList blocks_to_delete; - QList tracks_affected; foreach (TimelineViewBlockItem* item, selected_list) { Block* b = item->block(); blocks_to_delete.append(b); - - if (!tracks_affected.contains(item->Track())) { - tracks_affected.append(item->Track()); - } } // No-op if nothing is selected diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 2602aa97c..cb9bf5d64 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -103,6 +103,8 @@ public: void RestoreSplitterState(const QByteArray& state); + static void ReplaceBlocksWithGaps(const QList& blocks, bool remove_from_graph, QUndoCommand* command); + signals: void BlocksSelected(const QList& selected_blocks); @@ -460,8 +462,6 @@ private: void InsertGapsAt(const rational& time, const rational& length, QUndoCommand* command); - void ReplaceBlocksWithGaps(const QList& blocks, bool remove_from_graph, QUndoCommand* command); - void SetBlockLinksSelected(Block *block, bool selected); QVector GetEditToInfo(const rational &playhead_time, Timeline::MovementMode mode); From 4c30773fd4b9358c0b70539fe486f0036a84f39d Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 28 Sep 2020 19:55:18 +1000 Subject: [PATCH 06/74] nodeparamview: reworked based on QMainWindow for docking functionality --- app/widget/nodeparamview/nodeparamview.cpp | 57 +++++++++----- app/widget/nodeparamview/nodeparamview.h | 33 ++++++++- .../nodeparamview/nodeparamviewitem.cpp | 74 +++++++++++++++---- app/widget/nodeparamview/nodeparamviewitem.h | 25 ++++++- 4 files changed, 149 insertions(+), 40 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 34fea246d..5a9c50c64 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -49,18 +49,26 @@ NodeParamView::NodeParamView(QWidget *parent) : splitter->addWidget(scroll_area); // Param widget - param_widget_area_ = new QWidget(); - scroll_area->setWidget(param_widget_area_); + param_widget_container_ = new NodeParamViewParamContainer(); + connect(param_widget_container_, &NodeParamViewParamContainer::Resized, this, &NodeParamView::UpdateGlobalScrollBar); + scroll_area->setWidget(param_widget_container_); - // Set up scroll area layout - param_layout_ = new QVBoxLayout(param_widget_area_); - param_layout_->setSpacing(0); + param_widget_area_ = new QMainWindow(); - // KeyframeView is offset by a ruler, so to stay synchronized with it, we should be too - param_layout_->setContentsMargins(0, ruler()->height(), 0, 0); + // Disable dock widgets from tabbing and disable glitchy animations + param_widget_area_->setDockOptions(static_cast(0)); - // Add a stretch to allow empty space at the bottom of the layout - param_layout_->addStretch(); + // HACK: Hide the main window separators (unfortunately the cursors still appear) + param_widget_area_->setStyleSheet(QStringLiteral("QMainWindow::separator {background: rgba(0, 0, 0, 0)}")); + + QVBoxLayout* param_widget_container_layout = new QVBoxLayout(param_widget_container_); + QMargins param_widget_margin = param_widget_container_layout->contentsMargins(); + param_widget_margin.setTop(ruler()->height()); + param_widget_container_layout->setContentsMargins(param_widget_margin); + param_widget_container_layout->setSpacing(0); + param_widget_container_layout->addWidget(param_widget_area_); + + param_widget_container_layout->addStretch(INT_MAX); // Set up keyframe view QWidget* keyframe_area = new QWidget(); @@ -100,9 +108,8 @@ NodeParamView::NodeParamView(QWidget *parent) : layout->addWidget(vertical_scrollbar_); // Connect scrollbars together - connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, vertical_scrollbar_, &QScrollBar::setRange); - connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, this, &NodeParamView::ForceKeyframeViewToScroll); - + //connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, vertical_scrollbar_, &QScrollBar::setRange); + //connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, this, &NodeParamView::ForceKeyframeViewToScroll); connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue); connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); @@ -125,27 +132,29 @@ NodeParamView::NodeParamView(QWidget *parent) : void NodeParamView::SelectNodes(const QList &nodes) { foreach (Node* n, nodes) { - NodeParamViewItem* item = new NodeParamViewItem(n); + NodeParamViewItem* item = new NodeParamViewItem(n, param_widget_area_); - // Insert the widget before the stretch - param_layout_->insertWidget(param_layout_->count() - 1, item); + item->setAllowedAreas(Qt::LeftDockWidgetArea); + item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); connect(item, &NodeParamViewItem::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); connect(item, &NodeParamViewItem::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::ItemRequestedTimeChanged); connect(item, &NodeParamViewItem::InputDoubleClicked, this, &NodeParamView::InputDoubleClicked); connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode); + connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate); // Set time target item->SetTimeTarget(GetTimeTarget()); items_.insert(n, item); + param_widget_area_->addDockWidget(Qt::LeftDockWidgetArea, item); } UpdateItemTime(GetTimestamp()); // Re-arrange keyframes - QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection); + QueueKeyframePositionUpdate(); } void NodeParamView::DeselectNodes(const QList &nodes) @@ -159,7 +168,7 @@ void NodeParamView::DeselectNodes(const QList &nodes) } // Re-arrange keyframes - QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection); + QueueKeyframePositionUpdate(); } void NodeParamView::resizeEvent(QResizeEvent *event) @@ -167,6 +176,8 @@ void NodeParamView::resizeEvent(QResizeEvent *event) QWidget::resizeEvent(event); vertical_scrollbar_->setPageStep(vertical_scrollbar_->height()); + + UpdateGlobalScrollBar(); } void NodeParamView::ScaleChangedEvent(const double &scale) @@ -223,14 +234,22 @@ void NodeParamView::UpdateItemTime(const int64_t ×tamp) } } +void NodeParamView::QueueKeyframePositionUpdate() +{ + QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection); +} + void NodeParamView::ItemRequestedTimeChanged(const rational &time) { SetTimeAndSignal(Timecode::time_to_timestamp(time, keyframe_view_->timebase())); } -void NodeParamView::ForceKeyframeViewToScroll() +void NodeParamView::UpdateGlobalScrollBar() { - keyframe_view_->SetMaxScroll(param_widget_area_->height() - ruler()->height()); + int height_offscreen = param_widget_container_->height() - ruler()->height(); + + keyframe_view_->SetMaxScroll(height_offscreen); + vertical_scrollbar_->setRange(0, height_offscreen - keyframe_view_->height()); } void NodeParamView::PlaceKeyframesOnView() diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 1e3889b0c..6abc18235 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -21,6 +21,7 @@ #ifndef NODEPARAMVIEW_H #define NODEPARAMVIEW_H +#include #include #include @@ -31,6 +32,28 @@ OLIVE_NAMESPACE_ENTER +class NodeParamViewParamContainer : public QWidget +{ + Q_OBJECT +public: + NodeParamViewParamContainer(QWidget* parent = nullptr) : + QWidget(parent) + { + } + +protected: + virtual void resizeEvent(QResizeEvent *event) override + { + QWidget::resizeEvent(event); + + emit Resized(event->size().height()); + } + +signals: + void Resized(int new_height); + +}; + class NodeParamView : public TimeBasedWidget { Q_OBJECT @@ -66,7 +89,7 @@ protected: private: void UpdateItemTime(const int64_t ×tamp); - QVBoxLayout* param_layout_; + void QueueKeyframePositionUpdate(); KeyframeView* keyframe_view_; @@ -76,12 +99,16 @@ private: int last_scroll_val_; - QWidget* param_widget_area_; + NodeParamViewParamContainer* param_widget_container_; + + // This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows + // docking windows + QMainWindow* param_widget_area_; private slots: void ItemRequestedTimeChanged(const rational& time); - void ForceKeyframeViewToScroll(); + void UpdateGlobalScrollBar(); void PlaceKeyframesOnView(); diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index d9093079e..2c436f305 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -32,16 +32,11 @@ OLIVE_NAMESPACE_ENTER NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : - QWidget(parent), + QDockWidget(parent), node_(node) { - QVBoxLayout* main_layout = new QVBoxLayout(this); - main_layout->setSpacing(0); - main_layout->setMargin(0); - // Create title bar widget title_bar_ = new NodeParamViewItemTitleBar(this); - title_bar_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); QHBoxLayout* title_bar_layout = new QHBoxLayout(title_bar_); @@ -52,7 +47,7 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : title_bar_layout->addWidget(title_bar_lbl_); // Add title bar to widget - main_layout->addWidget(title_bar_); + this->setTitleBarWidget(title_bar_); // Create and add contents widget QVector inputs; @@ -70,11 +65,24 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); connect(body_, &NodeParamViewItemBody::KeyframeAdded, this, &NodeParamViewItem::KeyframeAdded); connect(body_, &NodeParamViewItemBody::KeyframeRemoved, this, &NodeParamViewItem::KeyframeRemoved); - connect(title_bar_collapse_btn_, &QPushButton::toggled, body_, &NodeParamViewItemBody::setVisible); - main_layout->addWidget(body_); + connect(title_bar_collapse_btn_, &QPushButton::toggled, this, &NodeParamViewItem::SetExpanded); + connect(title_bar_, &NodeParamViewItemTitleBar::DoubleClicked, this, &NodeParamViewItem::ToggleExpanded); + + QWidget* body_container = new QWidget(); + body_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + QHBoxLayout* body_container_layout = new QHBoxLayout(body_container); + body_container_layout->setSpacing(0); + body_container_layout->setMargin(0); + body_container_layout->addWidget(body_); + this->setWidget(body_container); connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate); + setBackgroundRole(QPalette::Base); + setAutoFillBackground(true); + + setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + Retranslate(); } @@ -122,21 +130,55 @@ void NodeParamViewItem::Retranslate() body_->Retranslate(); } -NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) : - QWidget(parent) +void NodeParamViewItem::SetExpanded(bool e) { + body_->setVisible(e); + title_bar_->SetBorderVisible(e); + title_bar_collapse_btn_->setChecked(e); +} + +bool NodeParamViewItem::IsExpanded() const +{ + return body_->isVisible(); +} + +void NodeParamViewItem::ToggleExpanded() +{ + SetExpanded(!IsExpanded()); +} + +NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) : + QWidget(parent), + draw_border_(true) +{ +} + +void NodeParamViewItemTitleBar::SetBorderVisible(bool e) +{ + draw_border_ = e; + + update(); } void NodeParamViewItemTitleBar::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); - QPainter p(this); + if (draw_border_) { + QPainter p(this); - // Draw bottom border using text color - int bottom = height() - 1; - p.setPen(palette().text().color()); - p.drawLine(0, bottom, width(), bottom); + // Draw bottom border using text color + int bottom = height() - 1; + p.setPen(palette().text().color()); + p.drawLine(0, bottom, width(), bottom); + } +} + +void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + QWidget::mouseDoubleClickEvent(event); + + emit DoubleClicked(); } NodeParamViewItemBody::NodeParamViewItemBody(const QVector &inputs, QWidget *parent) : diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 6d6996219..e19fc0867 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -21,6 +21,7 @@ #ifndef NODEPARAMVIEWITEM_H #define NODEPARAMVIEWITEM_H +#include #include #include #include @@ -36,12 +37,25 @@ OLIVE_NAMESPACE_ENTER -class NodeParamViewItemTitleBar : public QWidget { +class NodeParamViewItemTitleBar : public QWidget +{ + Q_OBJECT public: NodeParamViewItemTitleBar(QWidget* parent = nullptr); + void SetBorderVisible(bool e); + +signals: + void DoubleClicked(); + protected: virtual void paintEvent(QPaintEvent *event) override; + + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + +private: + bool draw_border_; + }; class NodeParamViewItemBody : public QWidget { @@ -99,7 +113,7 @@ private slots: }; -class NodeParamViewItem : public QWidget +class NodeParamViewItem : public QDockWidget { Q_OBJECT public: @@ -111,6 +125,8 @@ public: Node* GetNode() const; + bool IsExpanded() const; + public slots: void SignalAllKeyframes(); @@ -125,6 +141,11 @@ signals: void RequestSelectNode(const QList& node); +public slots: + void SetExpanded(bool e); + + void ToggleExpanded(); + protected: virtual void changeEvent(QEvent *e) override; From dc56dc1d602a852acc0005b8af440296e94c372a Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 28 Sep 2020 21:50:55 +1000 Subject: [PATCH 07/74] nodeparamview: implemented pinning --- app/widget/nodeparamview/nodeparamview.cpp | 122 ++++++++++++++---- app/widget/nodeparamview/nodeparamview.h | 12 ++ .../nodeparamview/nodeparamviewitem.cpp | 41 +++--- app/widget/nodeparamview/nodeparamviewitem.h | 21 ++- 4 files changed, 150 insertions(+), 46 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 5a9c50c64..a8c6f8d3b 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -108,8 +108,6 @@ NodeParamView::NodeParamView(QWidget *parent) : layout->addWidget(vertical_scrollbar_); // Connect scrollbars together - //connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, vertical_scrollbar_, &QScrollBar::setRange); - //connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, this, &NodeParamView::ForceKeyframeViewToScroll); connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue); connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); @@ -131,44 +129,68 @@ NodeParamView::NodeParamView(QWidget *parent) : void NodeParamView::SelectNodes(const QList &nodes) { + active_nodes_.append(nodes); + + bool changes_made = false; + foreach (Node* n, nodes) { - NodeParamViewItem* item = new NodeParamViewItem(n, param_widget_area_); + if (!pinned_nodes_.contains(n)) { + NodeParamViewItem* item = new NodeParamViewItem(n, param_widget_area_); - item->setAllowedAreas(Qt::LeftDockWidgetArea); - item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); + item->setAllowedAreas(Qt::LeftDockWidgetArea); + item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); - connect(item, &NodeParamViewItem::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); - connect(item, &NodeParamViewItem::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); - connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::ItemRequestedTimeChanged); - connect(item, &NodeParamViewItem::InputDoubleClicked, this, &NodeParamView::InputDoubleClicked); - connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode); - connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate); + connect(item, &NodeParamViewItem::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); + connect(item, &NodeParamViewItem::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); + connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::ItemRequestedTimeChanged); + connect(item, &NodeParamViewItem::InputDoubleClicked, this, &NodeParamView::InputDoubleClicked); + connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode); + connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate); + connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::SignalNodeOrder); + connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode); - // Set time target - item->SetTimeTarget(GetTimeTarget()); + // Set time target + item->SetTimeTarget(GetTimeTarget()); - items_.insert(n, item); - param_widget_area_->addDockWidget(Qt::LeftDockWidgetArea, item); + items_.insert(n, item); + param_widget_area_->addDockWidget(Qt::LeftDockWidgetArea, item); + + changes_made = true; + } } - UpdateItemTime(GetTimestamp()); + if (changes_made) { + UpdateItemTime(GetTimestamp()); - // Re-arrange keyframes - QueueKeyframePositionUpdate(); + // Re-arrange keyframes + QueueKeyframePositionUpdate(); + + SignalNodeOrder(); + } } void NodeParamView::DeselectNodes(const QList &nodes) { // Remove item from map and delete the widget - foreach (Node* n, nodes) { - // Remove all keyframes from this node - keyframe_view_->RemoveKeyframesOfNode(n); + bool changes_made = false; - delete items_.take(n); + foreach (Node* n, nodes) { + if (!pinned_nodes_.contains(n)) { + // Remove all keyframes from this node + RemoveNode(n); + + changes_made = true; + } + + active_nodes_.removeOne(n); } - // Re-arrange keyframes - QueueKeyframePositionUpdate(); + if (changes_made) { + // Re-arrange keyframes + QueueKeyframePositionUpdate(); + + SignalNodeOrder(); + } } void NodeParamView::resizeEvent(QResizeEvent *event) @@ -239,6 +261,42 @@ void NodeParamView::QueueKeyframePositionUpdate() QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection); } +void NodeParamView::SignalNodeOrder() +{ + // Sort by item Y (apparently there's no way in Qt to get the order of dock widgets) + QList nodes; + QList item_ys; + + for (auto it=items_.cbegin(); it!=items_.cend(); it++) { + int item_y = it.value()->pos().y(); + + bool inserted = false; + + for (int i=0; i item_y) { + item_ys.insert(i, item_y); + nodes.insert(i, it.key()); + inserted = true; + break; + } + } + + if (!inserted) { + item_ys.append(item_y); + nodes.append(it.key()); + } + } + + emit NodeOrderChanged(nodes); +} + +void NodeParamView::RemoveNode(Node *n) +{ + keyframe_view_->RemoveKeyframesOfNode(n); + + delete items_.take(n); +} + void NodeParamView::ItemRequestedTimeChanged(const rational &time) { SetTimeAndSignal(Timecode::time_to_timestamp(time, keyframe_view_->timebase())); @@ -259,4 +317,20 @@ void NodeParamView::PlaceKeyframesOnView() } } +void NodeParamView::PinNode(bool pin) +{ + NodeParamViewItem* item = static_cast(sender()); + Node* node = item->GetNode(); + + if (pin) { + pinned_nodes_.append(node); + } else { + pinned_nodes_.removeOne(node); + + if (!active_nodes_.contains(node)) { + RemoveNode(node); + } + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 6abc18235..537482b4c 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -77,6 +77,8 @@ signals: void RequestSelectNode(const QList& target); + void NodeOrderChanged(const QList& nodes); + protected: virtual void resizeEvent(QResizeEvent *event) override; @@ -91,6 +93,10 @@ private: void QueueKeyframePositionUpdate(); + void SignalNodeOrder(); + + void RemoveNode(Node* n); + KeyframeView* keyframe_view_; QMap items_; @@ -105,6 +111,10 @@ private: // docking windows QMainWindow* param_widget_area_; + QList pinned_nodes_; + + QList active_nodes_; + private slots: void ItemRequestedTimeChanged(const rational& time); @@ -112,6 +122,8 @@ private slots: void PlaceKeyframesOnView(); + void PinNode(bool pin); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 2c436f305..6fcb95b91 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -38,14 +38,6 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : // Create title bar widget title_bar_ = new NodeParamViewItemTitleBar(this); - QHBoxLayout* title_bar_layout = new QHBoxLayout(title_bar_); - - title_bar_collapse_btn_ = new CollapseButton(); - title_bar_layout->addWidget(title_bar_collapse_btn_); - - title_bar_lbl_ = new QLabel(title_bar_); - title_bar_layout->addWidget(title_bar_lbl_); - // Add title bar to widget this->setTitleBarWidget(title_bar_); @@ -65,8 +57,8 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); connect(body_, &NodeParamViewItemBody::KeyframeAdded, this, &NodeParamViewItem::KeyframeAdded); connect(body_, &NodeParamViewItemBody::KeyframeRemoved, this, &NodeParamViewItem::KeyframeRemoved); - connect(title_bar_collapse_btn_, &QPushButton::toggled, this, &NodeParamViewItem::SetExpanded); - connect(title_bar_, &NodeParamViewItemTitleBar::DoubleClicked, this, &NodeParamViewItem::ToggleExpanded); + connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItem::SetExpanded); + connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItem::PinToggled); QWidget* body_container = new QWidget(); body_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); @@ -122,9 +114,9 @@ void NodeParamViewItem::Retranslate() node_->Retranslate(); if (node_->GetLabel().isEmpty()) { - title_bar_lbl_->setText(node_->Name()); + title_bar_->SetText(node_->Name()); } else { - title_bar_lbl_->setText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name())); + title_bar_->SetText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name())); } body_->Retranslate(); @@ -133,8 +125,7 @@ void NodeParamViewItem::Retranslate() void NodeParamViewItem::SetExpanded(bool e) { body_->setVisible(e); - title_bar_->SetBorderVisible(e); - title_bar_collapse_btn_->setChecked(e); + title_bar_->SetExpanded(e); } bool NodeParamViewItem::IsExpanded() const @@ -151,11 +142,29 @@ NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) : QWidget(parent), draw_border_(true) { + QHBoxLayout* layout = new QHBoxLayout(this); + + collapse_btn_ = new CollapseButton(); + connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged); + layout->addWidget(collapse_btn_); + + lbl_ = new QLabel(); + layout->addWidget(lbl_); + + // Place next buttons on the far side + layout->addStretch(); + + QPushButton* pin_btn = new QPushButton(QStringLiteral("P")); + pin_btn->setCheckable(true); + pin_btn->setFixedSize(pin_btn->sizeHint().height(), pin_btn->sizeHint().height()); + layout->addWidget(pin_btn); + connect(pin_btn, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled); } -void NodeParamViewItemTitleBar::SetBorderVisible(bool e) +void NodeParamViewItemTitleBar::SetExpanded(bool e) { draw_border_ = e; + collapse_btn_->setChecked(e); update(); } @@ -178,7 +187,7 @@ void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event) { QWidget::mouseDoubleClickEvent(event); - emit DoubleClicked(); + collapse_btn_->click(); } NodeParamViewItemBody::NodeParamViewItemBody(const QVector &inputs, QWidget *parent) : diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index e19fc0867..e97a150fd 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -43,10 +43,17 @@ class NodeParamViewItemTitleBar : public QWidget public: NodeParamViewItemTitleBar(QWidget* parent = nullptr); - void SetBorderVisible(bool e); + void SetExpanded(bool e); + + void SetText(const QString& s) + { + lbl_->setText(s); + } signals: - void DoubleClicked(); + void ExpandedStateChanged(bool e); + + void PinToggled(bool e); protected: virtual void paintEvent(QPaintEvent *event) override; @@ -56,6 +63,10 @@ protected: private: bool draw_border_; + QLabel* lbl_; + + CollapseButton* collapse_btn_; + }; class NodeParamViewItemBody : public QWidget { @@ -141,6 +152,8 @@ signals: void RequestSelectNode(const QList& node); + void PinToggled(bool e); + public slots: void SetExpanded(bool e); @@ -152,10 +165,6 @@ protected: private: NodeParamViewItemTitleBar* title_bar_; - QLabel* title_bar_lbl_; - - CollapseButton* title_bar_collapse_btn_; - NodeParamViewItemBody* body_; Node* node_; From 7c7db5c9331b6b1e98a46d4871ed15dcabe9f374 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 00:05:47 +0100 Subject: [PATCH 08/74] Check footage in use before deleteing Initial very hacky implementation that checks if the footgae is in use before deleting it and makes sure all refernences to it are properly cleaned up. Also added IsMedia() function to Node to simplify things a bit. --- app/node/input/media/audio/audio.cpp | 5 ++ app/node/input/media/audio/audio.h | 2 + app/node/input/media/media.cpp | 5 ++ app/node/input/media/media.h | 6 +++ app/node/input/media/video/video.cpp | 5 ++ app/node/input/media/video/video.h | 2 + app/node/node.cpp | 5 ++ app/node/node.h | 9 ++++ .../projectexplorer/projectexplorer.cpp | 51 +++++++++++++++++++ 9 files changed, 90 insertions(+) diff --git a/app/node/input/media/audio/audio.cpp b/app/node/input/media/audio/audio.cpp index 5f34543ec..49463aa22 100644 --- a/app/node/input/media/audio/audio.cpp +++ b/app/node/input/media/audio/audio.cpp @@ -27,6 +27,11 @@ Node *AudioInput::copy() const return new AudioInput(); } +Stream::Type AudioInput::type() const +{ + return Stream::kAudio; +} + QString AudioInput::Name() const { return tr("Audio Input"); diff --git a/app/node/input/media/audio/audio.h b/app/node/input/media/audio/audio.h index 62914b693..5cebdcc3c 100644 --- a/app/node/input/media/audio/audio.h +++ b/app/node/input/media/audio/audio.h @@ -32,6 +32,8 @@ public: virtual Node* copy() const override; + virtual Stream::Type type() const override; + virtual QString Name() const override; virtual QString ShortName() const override; virtual QString id() const override; diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp index c0e1b3feb..8bfe11bb9 100644 --- a/app/node/input/media/media.cpp +++ b/app/node/input/media/media.cpp @@ -50,6 +50,11 @@ void MediaInput::SetFootage(StreamPtr f) footage_input_->set_standard_value(QVariant::fromValue(f)); } +bool MediaInput::IsMedia() const +{ + return true; +} + void MediaInput::Retranslate() { footage_input_->set_name(tr("Footage")); diff --git a/app/node/input/media/media.h b/app/node/input/media/media.h index ba89f3168..8934e321e 100644 --- a/app/node/input/media/media.h +++ b/app/node/input/media/media.h @@ -23,6 +23,7 @@ #include "codec/decoder.h" #include "node/node.h" +#include "project/item/footage/stream.h" OLIVE_NAMESPACE_ENTER @@ -35,11 +36,16 @@ class MediaInput : public Node public: MediaInput(); + virtual Stream::Type type() const = 0; + virtual QList Category() const override; StreamPtr footage(); void SetFootage(StreamPtr f); + virtual bool IsMedia() const override; + + virtual void Retranslate() override; virtual NodeValueTable Value(NodeValueDatabase& value) const override; diff --git a/app/node/input/media/video/video.cpp b/app/node/input/media/video/video.cpp index d451daeaf..c02aeaf39 100644 --- a/app/node/input/media/video/video.cpp +++ b/app/node/input/media/video/video.cpp @@ -36,6 +36,11 @@ Node *VideoInput::copy() const return new VideoInput(); } +Stream::Type VideoInput::type() const +{ + return Stream::kVideo; +} + QString VideoInput::Name() const { return tr("Video Input"); diff --git a/app/node/input/media/video/video.h b/app/node/input/media/video/video.h index 3ef9f54e8..7000ea82b 100644 --- a/app/node/input/media/video/video.h +++ b/app/node/input/media/video/video.h @@ -35,6 +35,8 @@ public: virtual Node* copy() const override; + virtual Stream::Type type() const override; + virtual QString Name() const override; virtual QString ShortName() const override; virtual QString id() const override; diff --git a/app/node/node.cpp b/app/node/node.cpp index 15e28512a..feda8c540 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -440,6 +440,11 @@ bool Node::IsTrack() const return false; } +bool Node::IsMedia() const +{ + return false; +} + const QList& Node::parameters() const { return params_; diff --git a/app/node/node.h b/app/node/node.h index ba05359ce..478d1e1ab 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -363,6 +363,15 @@ public: */ virtual bool IsTrack() const; + + /** + * @brief Returns whether this Node is a "Media" type or not + * + * You shouldn't ever need to override this since all derivatives of Media will automatically have this set to true. + * It's just a more convenient way of checking than dynamic_casting. + */ + virtual bool IsMedia() const; + /** * @brief The main processing function * diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 0a1dd0ab7..b47cbbaa7 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -558,6 +558,57 @@ void ProjectExplorer::DeleteSelected() } } + if (item_ptr->type() == Item::kFootage) { + // Get all sequences + QList sequences = model_.project()->get_items_of_type(Item::kSequence); + + // If no sequences exist we don't need to do anything clever here + if (!sequences.isEmpty()) { + // Footage can contain multiple streams, all of which need to be dealt with + foreach (StreamPtr stream, static_cast(item_ptr.get())->streams()) { + + // Check each sequence to see if it contains the footage in question + foreach (ItemPtr seq, sequences) { + + Sequence* s = static_cast(seq.get()); + + // Loop through nodes to find our Footage node + foreach (Node* node, s->nodes()) { + + // Check if node is of the right type + if (node->IsMedia() && static_cast(node)->type() == stream.get()->type()) { + // Check the streams are the same + if (static_cast(node)->footage() == stream) { + // Loop through nodes and set any that point to the Footage node to null + foreach (Node* check_node, s->nodes()) { + // Skip itself + if (check_node == node) { + continue; + } + if (check_node->GetImmediateDependencies().contains(node)) { + QList inputs = check_node->GetInputsIncludingArrays(); + + foreach (NodeInput* input, inputs) { + foreach (NodeEdgePtr edge, input->edges()) { + Node* connected = edge->output()->parentNode(); + + if (connected == node) { + input->DisconnectEdge(edge); + } + } + } + } + } + static_cast(node)->SetFootage(nullptr); + break; + } + } + } + } + } + } + } + new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); } From 3ed0cb2ab9992d45a4033d4ca930fba4be01bd4f Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 01:33:31 +0100 Subject: [PATCH 09/74] Move search code to seperate function. --- .../projectexplorer/projectexplorer.cpp | 96 ++++++++++++------- app/widget/projectexplorer/projectexplorer.h | 6 ++ 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index b47cbbaa7..3e216ec4b 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -536,6 +536,43 @@ void ProjectExplorer::DeselectAll() CurrentView()->selectionModel()->clearSelection(); } +QList ProjectExplorer::GetItemNodes(Item* item, Item::Type type) +{ + // Output list list + QList nodes; + + // Get all sequences. + QList sequences = model_.project()->get_items_of_type(Item::kSequence); + // Get item pointer. + ItemPtr item_ptr = item->get_shared_ptr(); + + if (type == Item::kFootage) { + // If no sequences exist we don't need to do anything clever here + if (!sequences.isEmpty()) { + // Footage can contain multiple streams, all of which need to be dealt with + foreach (StreamPtr stream, static_cast(item_ptr.get())->streams()) { + // Check each sequence to see if it contains the footage in question + foreach (ItemPtr seq, sequences) { + Sequence* s = static_cast(seq.get()); + + // Loop through nodes to find our Footage node + foreach (Node* node, s->nodes()) { + // Check if node is of the right type + if (node->IsMedia() && static_cast(node)->type() == stream.get()->type() || + static_cast(node)->type() == Stream::kImage) { + // Check the streams are the same + if (static_cast(node)->footage() == stream) { + nodes.append(node); + } + } + } + } + } + } + } + return nodes; +} + void ProjectExplorer::DeleteSelected() { QList selected = SelectedItems(); @@ -558,53 +595,44 @@ void ProjectExplorer::DeleteSelected() } } + // If this is a footage item, clean up if necessary if (item_ptr->type() == Item::kFootage) { - // Get all sequences - QList sequences = model_.project()->get_items_of_type(Item::kSequence); + + // Check if nodes exists + QList nodes = GetItemNodes(item, Item::kFootage); + if (!nodes.isEmpty()){ + // Loop through Footage nodes + foreach (Node* node, nodes) { - // If no sequences exist we don't need to do anything clever here - if (!sequences.isEmpty()) { - // Footage can contain multiple streams, all of which need to be dealt with - foreach (StreamPtr stream, static_cast(item_ptr.get())->streams()) { - - // Check each sequence to see if it contains the footage in question + // Loop through sequences + QList sequences = model_.project()->get_items_of_type(Item::kSequence); foreach (ItemPtr seq, sequences) { - Sequence* s = static_cast(seq.get()); - // Loop through nodes to find our Footage node - foreach (Node* node, s->nodes()) { + // Check all nodes to see if they're linked + foreach (Node* check_node, s->nodes()) { + // Skip itself + if (nodes.contains(check_node)) { + continue; + } + if (check_node->GetImmediateDependencies().contains(node)) { + QList inputs = check_node->GetInputsIncludingArrays(); - // Check if node is of the right type - if (node->IsMedia() && static_cast(node)->type() == stream.get()->type()) { - // Check the streams are the same - if (static_cast(node)->footage() == stream) { - // Loop through nodes and set any that point to the Footage node to null - foreach (Node* check_node, s->nodes()) { - // Skip itself - if (check_node == node) { - continue; - } - if (check_node->GetImmediateDependencies().contains(node)) { - QList inputs = check_node->GetInputsIncludingArrays(); + foreach (NodeInput* input, inputs) { + foreach (NodeEdgePtr edge, input->edges()) { + Node* connected = edge->output()->parentNode(); - foreach (NodeInput* input, inputs) { - foreach (NodeEdgePtr edge, input->edges()) { - Node* connected = edge->output()->parentNode(); - - if (connected == node) { - input->DisconnectEdge(edge); - } - } - } + if (connected == node) { + input->DisconnectEdge(edge); } } - static_cast(node)->SetFootage(nullptr); - break; } } } } + + // Set footage to be null + static_cast(node)->SetFootage(nullptr); } } } diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 2fe097bbd..6ec2f9af9 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -85,6 +85,12 @@ public: void DeleteSelected(); + /** + * @brief Check if an item is in use anywhere and return any relevant input nodes + * kFootage has two streams that need to be handled + */ + QList GetItemNodes(Item* item, Item::Type type); + public slots: void set_view_type(ProjectToolbar::ViewType type); From 0e272b6bba2c84e27b944198f80842ab873e6bd3 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 15:52:14 +0100 Subject: [PATCH 10/74] Cleanup and add warning message. --- .../projectexplorer/projectexplorer.cpp | 66 +++++++++---------- app/widget/projectexplorer/projectexplorer.h | 26 ++++++-- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 3e216ec4b..fabf30cab 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -558,8 +559,7 @@ QList ProjectExplorer::GetItemNodes(Item* item, Item::Type type) // Loop through nodes to find our Footage node foreach (Node* node, s->nodes()) { // Check if node is of the right type - if (node->IsMedia() && static_cast(node)->type() == stream.get()->type() || - static_cast(node)->type() == Stream::kImage) { + if (node->IsMedia() && static_cast(node)->type() == stream.get()->type()) { // Check the streams are the same if (static_cast(node)->footage() == stream) { nodes.append(node); @@ -573,6 +573,28 @@ QList ProjectExplorer::GetItemNodes(Item* item, Item::Type type) return nodes; } +ProjectExplorer::FootageDeleteResponse ProjectExplorer::DeleteWarningMessage() +{ + QMessageBox msgBox; + msgBox.setText(tr("This footage is in use.")); + msgBox.setInformativeText(tr("Do you want to offline the footage or entirely delete it from the timeline?")); + QPushButton* offline = msgBox.addButton(tr("Offline Footage"), QMessageBox::ApplyRole); + QPushButton* deleteClips = msgBox.addButton(tr("Delete Clips"), QMessageBox::ApplyRole); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setIcon(QMessageBox::Warning); + + msgBox.exec(); + + if (msgBox.clickedButton() == offline) { + return kOffline; + } + if (msgBox.clickedButton() == deleteClips) { + return kDelete; + } + + return kCancel; +} + void ProjectExplorer::DeleteSelected() { QList selected = SelectedItems(); @@ -601,38 +623,16 @@ void ProjectExplorer::DeleteSelected() // Check if nodes exists QList nodes = GetItemNodes(item, Item::kFootage); if (!nodes.isEmpty()){ - // Loop through Footage nodes - foreach (Node* node, nodes) { - - // Loop through sequences - QList sequences = model_.project()->get_items_of_type(Item::kSequence); - foreach (ItemPtr seq, sequences) { - Sequence* s = static_cast(seq.get()); - - // Check all nodes to see if they're linked - foreach (Node* check_node, s->nodes()) { - // Skip itself - if (nodes.contains(check_node)) { - continue; - } - if (check_node->GetImmediateDependencies().contains(node)) { - QList inputs = check_node->GetInputsIncludingArrays(); - - foreach (NodeInput* input, inputs) { - foreach (NodeEdgePtr edge, input->edges()) { - Node* connected = edge->output()->parentNode(); - - if (connected == node) { - input->DisconnectEdge(edge); - } - } - } - } - } + FootageDeleteResponse response = DeleteWarningMessage(); + if (response == kOffline) { + // Loop through Footage nodes + foreach (Node* node, nodes) { + // Set footage to be null + static_cast(node)->SetFootage(nullptr); } - - // Set footage to be null - static_cast(node)->SetFootage(nullptr); + } + if (response == kCancel) { + return; } } } diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 6ec2f9af9..0e69b427c 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -85,12 +85,6 @@ public: void DeleteSelected(); - /** - * @brief Check if an item is in use anywhere and return any relevant input nodes - * kFootage has two streams that need to be handled - */ - QList GetItemNodes(Item* item, Item::Type type); - public slots: void set_view_type(ProjectToolbar::ViewType type); @@ -107,6 +101,26 @@ signals: void DoubleClickedItem(Item* item); private: + enum FootageDeleteResponse { + kDelete, + kOffline, + kCancel + }; + + + /** + * @brief Pop up a QMessageBox to warn the user if the deleted clips are in use + * + * Returns a FootageDeleteResponse + */ + FootageDeleteResponse DeleteWarningMessage(); + + /** + * @brief Check if an item is in use anywhere and return any relevant input nodes + * kFootage has two streams that need to be handled + */ + QList GetItemNodes(Item* item, Item::Type type); + /** * @brief Simple convenience function for adding a view to this stacked widget * From 0f1d4ec1b829a426f46932b2bba24d809fb75762 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 15:59:51 +0100 Subject: [PATCH 11/74] Make sure image sequences are caught. --- app/widget/projectexplorer/projectexplorer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index fabf30cab..419f6a1ea 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -559,7 +559,7 @@ QList ProjectExplorer::GetItemNodes(Item* item, Item::Type type) // Loop through nodes to find our Footage node foreach (Node* node, s->nodes()) { // Check if node is of the right type - if (node->IsMedia() && static_cast(node)->type() == stream.get()->type()) { + if (node->IsMedia()){ // Check the streams are the same if (static_cast(node)->footage() == stream) { nodes.append(node); From c1e345611de58fb09e4c467222d8b618a7a1e9a5 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 16:14:56 +0100 Subject: [PATCH 12/74] Cleanup. --- app/widget/projectexplorer/projectexplorer.cpp | 6 +++--- app/widget/projectexplorer/projectexplorer.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 419f6a1ea..0694185e3 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -537,7 +537,7 @@ void ProjectExplorer::DeselectAll() CurrentView()->selectionModel()->clearSelection(); } -QList ProjectExplorer::GetItemNodes(Item* item, Item::Type type) +QList ProjectExplorer::GetFootageNodes(Item* item) { // Output list list QList nodes; @@ -547,7 +547,7 @@ QList ProjectExplorer::GetItemNodes(Item* item, Item::Type type) // Get item pointer. ItemPtr item_ptr = item->get_shared_ptr(); - if (type == Item::kFootage) { + if (item_ptr.get()->type() == Item::kFootage) { // If no sequences exist we don't need to do anything clever here if (!sequences.isEmpty()) { // Footage can contain multiple streams, all of which need to be dealt with @@ -621,7 +621,7 @@ void ProjectExplorer::DeleteSelected() if (item_ptr->type() == Item::kFootage) { // Check if nodes exists - QList nodes = GetItemNodes(item, Item::kFootage); + QList nodes = GetFootageNodes(item); if (!nodes.isEmpty()){ FootageDeleteResponse response = DeleteWarningMessage(); if (response == kOffline) { diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 0e69b427c..12c999d5e 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -117,9 +117,9 @@ private: /** * @brief Check if an item is in use anywhere and return any relevant input nodes - * kFootage has two streams that need to be handled + * Returns a QList as Footage has two streams that need to be handled */ - QList GetItemNodes(Item* item, Item::Type type); + QList GetFootageNodes(Item* item); /** * @brief Simple convenience function for adding a view to this stacked widget From 5bb9cb0ccad27544898bbe7d5e0af36ccbf5f71f Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 17:42:09 +0100 Subject: [PATCH 13/74] Make Offlining footage an UndoCommand Created a new UndoCommand in ProjectViewModel that set the footage input to a nullptr and resets it to the correct stream on undo. --- app/project/projectviewmodel.cpp | 38 +++++++++++++++++++ app/project/projectviewmodel.h | 21 ++++++++++ .../projectexplorer/projectexplorer.cpp | 27 +++++++------ app/widget/projectexplorer/projectexplorer.h | 2 +- 4 files changed, 75 insertions(+), 13 deletions(-) diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index 7dfaf453a..8c176f24f 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -25,6 +25,7 @@ #include #include "core.h" +#include "node/input/media/media.h" OLIVE_NAMESPACE_ENTER @@ -603,4 +604,41 @@ void ProjectViewModel::RemoveItemCommand::undo_internal() model_->AddChild(parent_, item_); } +ProjectViewModel::OfflineFootageCommand::OfflineFootageCommand(ProjectViewModel* model, ItemPtr item, + QMap nodes, QUndoCommand* parent) : + UndoCommand(parent), + model_(model), + item_(item), + nodes_(nodes) +{ +} + +Project *ProjectViewModel::OfflineFootageCommand::GetRelevantProject() const +{ + return model_->project(); +} + +void ProjectViewModel::OfflineFootageCommand::redo_internal() +{ + QMap::const_iterator it = nodes_.constBegin(); + while (it != nodes_.constEnd()) { + static_cast(it.key())->SetFootage(nullptr); + ++it; + } + + parent_ = item_->parent(); + model_->RemoveChild(parent_, item_.get()); +} + +void ProjectViewModel::OfflineFootageCommand::undo_internal() +{ + model_->AddChild(parent_, item_); + + QMap::const_iterator it = nodes_.constBegin(); + while (it != nodes_.constEnd()) { + static_cast(it.key())->SetFootage(it.value()); + ++it; + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index 964378316..d79542aa0 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -194,6 +194,27 @@ public: }; + class OfflineFootageCommand : public UndoCommand { + public: + OfflineFootageCommand(ProjectViewModel* model, ItemPtr item, QMap nodes, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo_internal() override; + + virtual void undo_internal() override; + + private: + ProjectViewModel* model_; + + ItemPtr item_; + + Item* parent_; + + QMap nodes_; + }; + private: /** * @brief Retrieve the index of `item` in its parent diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 0694185e3..3c5ce6f07 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -537,11 +537,11 @@ void ProjectExplorer::DeselectAll() CurrentView()->selectionModel()->clearSelection(); } -QList ProjectExplorer::GetFootageNodes(Item* item) +QMap ProjectExplorer::GetFootageNodes(Item* item) { // Output list list - QList nodes; - + QMap nodes; + // Get all sequences. QList sequences = model_.project()->get_items_of_type(Item::kSequence); // Get item pointer. @@ -562,7 +562,7 @@ QList ProjectExplorer::GetFootageNodes(Item* item) if (node->IsMedia()){ // Check the streams are the same if (static_cast(node)->footage() == stream) { - nodes.append(node); + nodes.insert(node, stream); } } } @@ -615,29 +615,32 @@ void ProjectExplorer::DeleteSelected() if (Core::instance()->main_window()->IsSequenceOpen(s)) { Core::instance()->main_window()->CloseSequence(s); } + + new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); } // If this is a footage item, clean up if necessary if (item_ptr->type() == Item::kFootage) { // Check if nodes exists - QList nodes = GetFootageNodes(item); + QMap nodes = GetFootageNodes(item); if (!nodes.isEmpty()){ + // Warn user and ask them what to do FootageDeleteResponse response = DeleteWarningMessage(); if (response == kOffline) { - // Loop through Footage nodes - foreach (Node* node, nodes) { - // Set footage to be null - static_cast(node)->SetFootage(nullptr); - } + new ProjectViewModel::OfflineFootageCommand(&model_, item_ptr, nodes, command); + } + if (response == kDelete) { + // Get all sequences. + QList sequences = model_.project()->get_items_of_type(Item::kSequence); + } if (response == kCancel) { + delete command; return; } } } - - new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); } Core::instance()->undo_stack()->pushIfHasChildren(command); diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 12c999d5e..b755401ac 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -119,7 +119,7 @@ private: * @brief Check if an item is in use anywhere and return any relevant input nodes * Returns a QList as Footage has two streams that need to be handled */ - QList GetFootageNodes(Item* item); + QMap GetFootageNodes(Item* item); /** * @brief Simple convenience function for adding a view to this stacked widget From 602f96b2b6e63ff3a0e8369941a1edcefc765edf Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 22:15:32 +0100 Subject: [PATCH 14/74] Cleanup comment --- app/widget/projectexplorer/projectexplorer.cpp | 1 - app/widget/projectexplorer/projectexplorer.h | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 3c5ce6f07..e0ca1aff2 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -577,7 +577,6 @@ ProjectExplorer::FootageDeleteResponse ProjectExplorer::DeleteWarningMessage() { QMessageBox msgBox; msgBox.setText(tr("This footage is in use.")); - msgBox.setInformativeText(tr("Do you want to offline the footage or entirely delete it from the timeline?")); QPushButton* offline = msgBox.addButton(tr("Offline Footage"), QMessageBox::ApplyRole); QPushButton* deleteClips = msgBox.addButton(tr("Delete Clips"), QMessageBox::ApplyRole); msgBox.setStandardButtons(QMessageBox::Cancel); diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index b755401ac..907da2964 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -117,7 +117,8 @@ private: /** * @brief Check if an item is in use anywhere and return any relevant input nodes - * Returns a QList as Footage has two streams that need to be handled + * + * Returns a QMap pairing a Footage node to its StreamPtr */ QMap GetFootageNodes(Item* item); From fb6e8a8a13917a78c66238f8357d8dc6f5a6f0ab Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 29 Sep 2020 00:40:40 +1000 Subject: [PATCH 15/74] volume: disable keyframing on the sample input --- app/node/audio/volume/volume.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp index 38a6cc7da..4b403118b 100644 --- a/app/node/audio/volume/volume.cpp +++ b/app/node/audio/volume/volume.cpp @@ -25,6 +25,7 @@ OLIVE_NAMESPACE_ENTER VolumeNode::VolumeNode() { samples_input_ = new NodeInput("samples_in", NodeParam::kSamples); + samples_input_->set_is_keyframable(false); AddInput(samples_input_); volume_input_ = new NodeInput("volume_in", NodeParam::kFloat, 1.0); From 610f4c54fe58e806ca822413469a8109f49e8358 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 29 Sep 2020 00:42:16 +1000 Subject: [PATCH 16/74] various: began heavy rework of curve view to link closely with param view --- app/panel/curve/curve.cpp | 18 +- app/panel/curve/curve.h | 4 +- app/panel/param/param.cpp | 76 +-------- app/panel/param/param.h | 17 +- app/widget/CMakeLists.txt | 5 +- app/widget/curvewidget/curveview.cpp | 55 +++++- app/widget/curvewidget/curveview.h | 8 + app/widget/curvewidget/curvewidget.cpp | 167 ++++++++----------- app/widget/curvewidget/curvewidget.h | 31 ++-- app/widget/keyframeview/keyframeviewbase.cpp | 13 +- app/widget/keyframeview/keyframeviewbase.h | 2 + app/widget/nodeparamview/nodeparamview.cpp | 4 + app/widget/nodeparamview/nodeparamview.h | 2 + app/widget/nodetreeview/CMakeLists.txt | 22 +++ app/widget/nodetreeview/nodetreeview.cpp | 111 ++++++++++++ app/widget/nodetreeview/nodetreeview.h | 62 +++++++ app/window/mainwindow/mainwindow.cpp | 41 +++-- app/window/mainwindow/mainwindow.h | 4 +- 18 files changed, 386 insertions(+), 256 deletions(-) create mode 100644 app/widget/nodetreeview/CMakeLists.txt create mode 100644 app/widget/nodetreeview/nodetreeview.cpp create mode 100644 app/widget/nodetreeview/nodetreeview.h diff --git a/app/panel/curve/curve.cpp b/app/panel/curve/curve.cpp index b6179d258..fd6e418ae 100644 --- a/app/panel/curve/curve.cpp +++ b/app/panel/curve/curve.cpp @@ -32,21 +32,14 @@ CurvePanel::CurvePanel(QWidget *parent) : Retranslate(); } -NodeInput *CurvePanel::GetInput() const -{ - return static_cast(GetTimeBasedWidget())->GetInput(); -} - void CurvePanel::DeleteSelected() { static_cast(GetTimeBasedWidget())->DeleteSelected(); } -void CurvePanel::SetInput(NodeInput *input) +void CurvePanel::SetNodes(const QList &nodes) { - static_cast(GetTimeBasedWidget())->SetInput(input); - - Retranslate(); + static_cast(GetTimeBasedWidget())->SetNodes(nodes); } void CurvePanel::IncreaseTrackHeight() @@ -66,13 +59,6 @@ void CurvePanel::Retranslate() TimeBasedPanel::Retranslate(); SetTitle(tr("Curve Editor")); - - NodeInput* connected_input = static_cast(GetTimeBasedWidget())->GetInput(); - if (connected_input) { - SetSubtitle(connected_input->name()); - } else { - SetSubtitle(QString()); - } } OLIVE_NAMESPACE_EXIT diff --git a/app/panel/curve/curve.h b/app/panel/curve/curve.h index 982a4d0b4..375c42822 100644 --- a/app/panel/curve/curve.h +++ b/app/panel/curve/curve.h @@ -32,12 +32,10 @@ class CurvePanel : public TimeBasedPanel public: CurvePanel(QWidget* parent); - NodeInput* GetInput() const; - virtual void DeleteSelected() override; public slots: - void SetInput(NodeInput* input); + void SetNodes(const QList& nodes); virtual void IncreaseTrackHeight() override; diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index 519250837..5fa076690 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -28,9 +28,8 @@ ParamPanel::ParamPanel(QWidget* parent) : TimeBasedPanel(QStringLiteral("ParamPanel"), parent) { NodeParamView* view = new NodeParamView(); - connect(view, &NodeParamView::InputDoubleClicked, this, &ParamPanel::CreateCurvePanel); connect(view, &NodeParamView::RequestSelectNode, this, &ParamPanel::RequestSelectNode); - //connect(view, &NodeParamView::FoundGizmos, this, &ParamPanel::FoundGizmos); + connect(view, &NodeParamView::NodeOrderChanged, this, &ParamPanel::NodeOrderChanged); SetTimeBasedWidget(view); Retranslate(); @@ -50,14 +49,6 @@ void ParamPanel::DeselectNodes(const QList &nodes) Retranslate(); } -void ParamPanel::SetTimestamp(const int64_t ×tamp) -{ - TimeBasedPanel::SetTimestamp(timestamp); - - // Ensure all CurvePanels are updated with this time too - ParamViewTimeChanged(timestamp); -} - void ParamPanel::DeleteSelected() { static_cast(GetTimeBasedWidget())->DeleteSelected(); @@ -78,69 +69,4 @@ void ParamPanel::Retranslate() } } -void ParamPanel::CreateCurvePanel(NodeInput *input) -{ - if (!input->is_keyframable()) { - return; - } - - CurvePanel* panel = open_curve_panels_.value(input); - - if (panel) { - panel->raise(); - return; - } - - NodeParamView* view = static_cast(GetTimeBasedWidget()); - - panel = Core::instance()->main_window()->AppendCurvePanel(); - - 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); - connect(panel, &CurvePanel::CloseRequested, this, &ParamPanel::ClosingCurvePanel); - - open_curve_panels_.insert(input, panel); -} - -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 442c5e921..f74bce63a 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -37,31 +37,16 @@ public slots: void SelectNodes(const QList& nodes); void DeselectNodes(const QList& nodes); - virtual void SetTimestamp(const int64_t& timestamp) override; - virtual void DeleteSelected() override; signals: void RequestSelectNode(const QList& target); - void FoundGizmos(Node* node); + void NodeOrderChanged(const QList& nodes); protected: virtual void Retranslate() override; -private slots: - void CreateCurvePanel(NodeInput* input); - - 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/CMakeLists.txt b/app/widget/CMakeLists.txt index 1a05cbe55..17b954970 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -29,9 +29,10 @@ add_subdirectory(manageddisplay) add_subdirectory(menu) add_subdirectory(nodecombobox) add_subdirectory(nodecopypaste) -add_subdirectory(nodeview) -add_subdirectory(nodeparamview) add_subdirectory(nodetableview) +add_subdirectory(nodetreeview) +add_subdirectory(nodeparamview) +add_subdirectory(nodeview) add_subdirectory(panel) add_subdirectory(path) add_subdirectory(pixelsampler) diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index e8b6884ef..375d9604a 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -75,6 +75,55 @@ void CurveView::SetTrackVisible(int track, bool visible) SetKeyframeTrackVisible(track, visible); } +void CurveView::ConnectInput(NodeInput *input) +{ + if (connected_inputs_.contains(input)) { + // Input wasn't connected, do nothing + return; + } + + // Add keyframes from this input + foreach (const NodeInput::KeyframeTrack& track, input->keyframe_tracks()) { + foreach (NodeKeyframePtr key, track) { + this->AddKeyframe(key); + } + } + + // Append to the list + connected_inputs_.append(input); + + // Connect add/remove signals + connect(input, &NodeInput::KeyframeAdded, this, &CurveView::AddKeyframe); + connect(input, &NodeInput::KeyframeRemoved, this, &CurveView::RemoveKeyframe); +} + +void CurveView::DisconnectNode(Node *node) +{ + QList inputs = node->GetInputsIncludingArrays(); + + foreach (NodeInput* i, inputs) { + DisconnectInput(i); + } +} + +void CurveView::DisconnectInput(NodeInput *input) +{ + if (!connected_inputs_.contains(input)) { + // Input wasn't connected, do nothing + return; + } + + // Remove keyframes belonging to this input + RemoveKeyframesOfInput(input); + + // Remove from the list + connected_inputs_.removeOne(input); + + // Disconnect add/remove signals + disconnect(input, &NodeInput::KeyframeAdded, this, &CurveView::AddKeyframe); + disconnect(input, &NodeInput::KeyframeRemoved, this, &CurveView::RemoveKeyframe); +} + void CurveView::drawBackground(QPainter *painter, const QRectF &rect) { if (timebase().isNull()) { @@ -273,10 +322,8 @@ QList CurveView::GetKeyframesSortedByTime(int track) { QList sorted; - QMap::const_iterator iterator; - - for (iterator=item_map().begin();iterator!=item_map().end();iterator++) { - NodeKeyframe* key = iterator.key(); + for (auto it=item_map().cbegin();it!=item_map().cend();it++) { + NodeKeyframe* key = it.key(); if (key->track() != track) { continue; diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index e051f4f04..a5a9dce8c 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -42,6 +42,12 @@ public: void SetTrackVisible(int track, bool visible); + void ConnectInput(NodeInput* input); + + void DisconnectNode(Node* node); + + void DisconnectInput(NodeInput* input); + public slots: void AddKeyframe(NodeKeyframePtr key); @@ -86,6 +92,8 @@ private: QVector track_visible_; + QList connected_inputs_; + int track_count_; private slots: diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index da32088ec..41d2a88ef 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "core.h" @@ -34,12 +35,23 @@ OLIVE_NAMESPACE_ENTER CurveWidget::CurveWidget(QWidget *parent) : - TimeBasedWidget(parent), - input_(nullptr), - bridge_(nullptr) + TimeBasedWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); + QHBoxLayout* outer_layout = new QHBoxLayout(this); + + QSplitter* splitter = new QSplitter(); + outer_layout->addWidget(splitter); + + tree_view_ = new NodeTreeView(); + tree_view_->SetOnlyShowKeyframable(true); + connect(tree_view_, &NodeTreeView::NodeEnableChanged, this, &CurveWidget::NodeEnabledChanged); + connect(tree_view_, &NodeTreeView::InputEnableChanged, this, &CurveWidget::InputEnabledChanged); + splitter->addWidget(tree_view_); + + QWidget* workarea = new QWidget(); + QVBoxLayout* layout = new QVBoxLayout(workarea); layout->setMargin(0); + splitter->addWidget(workarea); QHBoxLayout* top_controls = new QHBoxLayout(); @@ -92,12 +104,8 @@ CurveWidget::CurveWidget(QWidget *parent) : view_->setHorizontalScrollBar(scrollbar()); connect(view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); - widget_bridge_layout_ = new QHBoxLayout(); - widget_bridge_layout_->addStretch(); - input_label_ = new QLabel(); - widget_bridge_layout_->addWidget(input_label_); - widget_bridge_layout_->addStretch(); - layout->addLayout(widget_bridge_layout_); + // Disable collapsing the main curve view (but allow collapsing the tree) + splitter->setCollapsible(1, false); SetScale(120.0); } @@ -108,71 +116,6 @@ CurveWidget::~CurveWidget() view_->Clear(); } -NodeInput *CurveWidget::GetInput() const -{ - return input_; -} - -void CurveWidget::SetInput(NodeInput *input) -{ - if (bridge_) { - foreach (QWidget* bridge_widget, bridge_->widgets()) { - bridge_widget->deleteLater(); - } - 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); - } - - view_->Clear(); - - input_ = input; - key_control_->SetInput(input_); - - if (input_) { - view_->SetTrackCount(input_->get_number_of_keyframe_tracks()); - - bridge_ = new NodeParamViewWidgetBridge(input_, this); - - bridge_->SetTimeTarget(GetTimeTarget()); - - for (int i=0;iwidgets().size();i++) { - // Insert between two stretches to center the widget - 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); - connect(input_, &NodeInput::KeyframeRemoved, view_, &CurveView::RemoveKeyframe); - - foreach (const NodeInput::KeyframeTrack& track, input_->keyframe_tracks()) { - foreach (NodeKeyframePtr key, track) { - view_->AddKeyframe(key); - } - } - } - - UpdateInputLabel(); - - QMetaObject::invokeMethod(view_, "ZoomToFit", Qt::QueuedConnection); -} - const double &CurveWidget::GetVerticalScale() { return view_->GetYScale(); @@ -188,12 +131,26 @@ void CurveWidget::DeleteSelected() view_->DeleteSelected(); } -void CurveWidget::changeEvent(QEvent *e) +void CurveWidget::SetNodes(const QList &nodes) { - if (e->type() == QEvent::LanguageChange) { - UpdateInputLabel(); + tree_view_->SetNodes(nodes); + + // Detect removed nodes + foreach (Node* n, nodes_) { + if (!nodes.contains(n)) { + view_->DisconnectNode(n); + } } - QWidget::changeEvent(e); + + // Detect added nodes + foreach (Node* n, nodes) { + if (tree_view_->IsNodeEnabled(n) && !nodes_.contains(n)) { + ConnectNode(n); + } + } + + // Save new node list + nodes_ = nodes; } void CurveWidget::TimeChangedEvent(const int64_t ×tamp) @@ -223,10 +180,6 @@ void CurveWidget::TimeTargetChangedEvent(Node *target) key_control_->SetTimeTarget(target); view_->SetTimeTarget(target); - - if (bridge_) { - bridge_->SetTimeTarget(target); - } } void CurveWidget::ConnectedNodeChanged(ViewerOutput *n) @@ -234,15 +187,6 @@ void CurveWidget::ConnectedNodeChanged(ViewerOutput *n) SetTimeTarget(n); } -void CurveWidget::UpdateInputLabel() -{ - if (input_) { - input_label_->setText(QStringLiteral("%1 :: %2:").arg(input_->parentNode()->Name(), input_->name())); - } else { - input_label_->clear(); - } -} - void CurveWidget::SetKeyframeButtonEnabled(bool enable) { linear_button_->setEnabled(enable); @@ -266,15 +210,26 @@ void CurveWidget::SetKeyframeButtonCheckedFromType(NodeKeyframe::Type type) void CurveWidget::UpdateBridgeTime(const int64_t ×tamp) { - if (!input_) { - return; - } - rational time = Timecode::timestamp_to_time(timestamp, view_->timebase()); - bridge_->SetTime(time); key_control_->SetTime(time); } +void CurveWidget::ConnectNode(Node *n) +{ + QList inputs = n->GetInputsIncludingArrays(); + + foreach (NodeInput* i, inputs) { + if (tree_view_->IsInputEnabled(i)) { + view_->ConnectInput(i); + } + } +} + +void CurveWidget::DisconnectNode(Node *n) +{ + view_->DisconnectNode(n); +} + void CurveWidget::SelectionChanged() { QList selected = view_->scene()->selectedItems(); @@ -349,4 +304,22 @@ void CurveWidget::KeyControlRequestedTimeChanged(const rational &time) SetTimeAndSignal(Timecode::time_to_timestamp(time, view_->timebase())); } +void CurveWidget::NodeEnabledChanged(Node* n, bool e) +{ + if (e) { + ConnectNode(n); + } else { + DisconnectNode(n); + } +} + +void CurveWidget::InputEnabledChanged(NodeInput *i, bool e) +{ + if (e) { + view_->ConnectInput(i); + } else { + view_->DisconnectInput(i); + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index 7c33b49c7..253ca6a71 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -30,6 +30,7 @@ #include "node/input.h" #include "widget/nodeparamview/nodeparamviewkeyframecontrol.h" #include "widget/nodeparamview/nodeparamviewwidgetbridge.h" +#include "widget/nodetreeview/nodetreeview.h" #include "widget/timebased/timebased.h" OLIVE_NAMESPACE_ENTER @@ -42,17 +43,15 @@ public: virtual ~CurveWidget() override; - NodeInput* GetInput() const; - void SetInput(NodeInput* input); - const double& GetVerticalScale(); void SetVerticalScale(const double& vscale); void DeleteSelected(); -protected: - virtual void changeEvent(QEvent *) override; +public slots: + void SetNodes(const QList& nodes); +protected: virtual void TimeChangedEvent(const int64_t &) override; virtual void TimebaseChangedEvent(const rational &) override; virtual void ScaleChangedEvent(const double &) override; @@ -62,8 +61,6 @@ protected: virtual void ConnectedNodeChanged(ViewerOutput* n) override; private: - void UpdateInputLabel(); - void SetKeyframeButtonEnabled(bool enable); void SetKeyframeButtonChecked(bool checked); @@ -72,6 +69,12 @@ private: void UpdateBridgeTime(const int64_t& timestamp); + void ConnectNode(Node* n); + + void DisconnectNode(Node* n); + + NodeTreeView* tree_view_; + QPushButton* linear_button_; QPushButton* bezier_button_; @@ -80,18 +83,12 @@ private: CurveView* view_; - NodeInput* input_; - - QLabel* input_label_; - - QHBoxLayout* widget_bridge_layout_; - - NodeParamViewWidgetBridge* bridge_; - NodeParamViewKeyframeControl* key_control_; QList checkboxes_; + QList nodes_; + private slots: void SelectionChanged(); @@ -99,6 +96,10 @@ private slots: void KeyControlRequestedTimeChanged(const rational& time); + void NodeEnabledChanged(Node* n, bool e); + + void InputEnabledChanged(NodeInput* i, bool e); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index 56ba3078d..c114faf81 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -79,10 +79,15 @@ void KeyframeViewBase::RemoveKeyframesOfNode(Node *n) QList inputs = n->GetInputsIncludingArrays(); foreach (NodeInput* i, inputs) { - foreach (const NodeInput::KeyframeTrack& track, i->keyframe_tracks()) { - foreach (NodeKeyframePtr key, track) { - RemoveKeyframe(key); - } + RemoveKeyframesOfInput(i); + } +} + +void KeyframeViewBase::RemoveKeyframesOfInput(NodeInput *i) +{ + foreach (const NodeInput::KeyframeTrack& track, i->keyframe_tracks()) { + foreach (NodeKeyframePtr key, track) { + RemoveKeyframe(key); } } } diff --git a/app/widget/keyframeview/keyframeviewbase.h b/app/widget/keyframeview/keyframeviewbase.h index 9cbe0b01f..c6d40fbd8 100644 --- a/app/widget/keyframeview/keyframeviewbase.h +++ b/app/widget/keyframeview/keyframeviewbase.h @@ -42,6 +42,8 @@ public: void RemoveKeyframesOfNode(Node* n); + void RemoveKeyframesOfInput(NodeInput* i); + public slots: void RemoveKeyframe(NodeKeyframePtr key); diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index a8c6f8d3b..389a1db97 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -139,6 +139,7 @@ void NodeParamView::SelectNodes(const QList &nodes) item->setAllowedAreas(Qt::LeftDockWidgetArea); item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); + item->SetExpanded(node_expanded_state_.value(n, true)); connect(item, &NodeParamViewItem::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); connect(item, &NodeParamViewItem::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); @@ -176,6 +177,9 @@ void NodeParamView::DeselectNodes(const QList &nodes) foreach (Node* n, nodes) { if (!pinned_nodes_.contains(n)) { + // Store expanded state + node_expanded_state_.insert(n, items_.value(n)->IsExpanded()); + // Remove all keyframes from this node RemoveNode(n); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 537482b4c..a807b2171 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -115,6 +115,8 @@ private: QList active_nodes_; + QMap node_expanded_state_; + private slots: void ItemRequestedTimeChanged(const rational& time); diff --git a/app/widget/nodetreeview/CMakeLists.txt b/app/widget/nodetreeview/CMakeLists.txt new file mode 100644 index 000000000..486f03d0a --- /dev/null +++ b/app/widget/nodetreeview/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2019 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + widget/nodetreeview/nodetreeview.h + widget/nodetreeview/nodetreeview.cpp + PARENT_SCOPE +) diff --git a/app/widget/nodetreeview/nodetreeview.cpp b/app/widget/nodetreeview/nodetreeview.cpp new file mode 100644 index 000000000..b02a707d9 --- /dev/null +++ b/app/widget/nodetreeview/nodetreeview.cpp @@ -0,0 +1,111 @@ +#include "nodetreeview.h" + +OLIVE_NAMESPACE_ENTER + +NodeTreeView::NodeTreeView(QWidget *parent) : + QTreeWidget(parent), + only_show_keyframable_(false) +{ + connect(this, &NodeTreeView::itemChanged, this, &NodeTreeView::ItemCheckStateChanged); + + Retranslate(); +} + +bool NodeTreeView::IsNodeEnabled(Node *n) const +{ + return !disabled_nodes_.contains(n); +} + +bool NodeTreeView::IsInputEnabled(NodeInput *i) const +{ + return !disabled_inputs_.contains(i); +} + +void NodeTreeView::SetNodes(const QList &nodes) +{ + nodes_ = nodes; + + this->clear(); + + foreach (Node* n, nodes_) { + QTreeWidgetItem* node_item = new QTreeWidgetItem(); + node_item->setText(0, n->Name()); + node_item->setCheckState(0, disabled_nodes_.contains(n) ? Qt::Unchecked : Qt::Checked); + node_item->setData(0, kItemType, kItemTypeNode); + node_item->setData(0, kItemPointer, reinterpret_cast(n)); + + QList inputs = n->GetInputsIncludingArrays(); + foreach (NodeInput* i, inputs) { + if (only_show_keyframable_ && !i->is_keyframable()) { + continue; + } + + QTreeWidgetItem* input_item = new QTreeWidgetItem(node_item); + input_item->setText(0, i->name()); + input_item->setCheckState(0, disabled_inputs_.contains(i) ? Qt::Unchecked : Qt::Checked); + input_item->setData(0, kItemType, kItemTypeInput); + input_item->setData(0, kItemPointer, reinterpret_cast(i)); + } + + // Add at the end to prevent unnecessary signalling while we're setting these objects up + if (node_item->childCount() > 0) { + this->addTopLevelItem(node_item); + } else { + delete node_item; + } + } +} + +void NodeTreeView::changeEvent(QEvent *e) +{ + QTreeWidget::changeEvent(e); + + if (e->type() == QEvent::LanguageChange) { + Retranslate(); + } +} + +void NodeTreeView::Retranslate() +{ + setHeaderLabel(tr("Nodes")); +} + +void NodeTreeView::ItemCheckStateChanged(QTreeWidgetItem *item, int column) +{ + Q_UNUSED(column) + + switch (item->data(0, kItemType).toInt()) { + case kItemTypeNode: + { + Node* n = reinterpret_cast(item->data(0, kItemPointer).value()); + + if (item->checkState(0) == Qt::Checked) { + if (disabled_nodes_.contains(n)) { + disabled_nodes_.removeOne(n); + emit NodeEnableChanged(n, true); + } + } else if (!disabled_nodes_.contains(n)) { + disabled_nodes_.append(n); + emit NodeEnableChanged(n, false); + } + break; + } + case kItemTypeInput: + { + NodeInput* i = reinterpret_cast(item->data(0, kItemPointer).value()); + + if (item->checkState(0) == Qt::Checked) { + if (disabled_inputs_.contains(i)) { + disabled_inputs_.removeOne(i); + emit InputEnableChanged(i, true); + } + } else if (!disabled_inputs_.contains(i)) { + disabled_inputs_.append(i); + emit InputEnableChanged(i, false); + } + break; + } + } +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodetreeview/nodetreeview.h b/app/widget/nodetreeview/nodetreeview.h new file mode 100644 index 000000000..2b221a68c --- /dev/null +++ b/app/widget/nodetreeview/nodetreeview.h @@ -0,0 +1,62 @@ +#ifndef NODETREEVIEW_H +#define NODETREEVIEW_H + +#include + +#include "node/node.h" + +OLIVE_NAMESPACE_ENTER + +class NodeTreeView : public QTreeWidget +{ + Q_OBJECT +public: + NodeTreeView(QWidget *parent = nullptr); + + bool IsNodeEnabled(Node* n) const; + + bool IsInputEnabled(NodeInput* i) const; + + void SetOnlyShowKeyframable(bool e) + { + only_show_keyframable_ = e; + } + +public slots: + void SetNodes(const QList& nodes); + +signals: + void NodeEnableChanged(Node* n, bool e); + + void InputEnableChanged(NodeInput* i, bool e); + +protected: + virtual void changeEvent(QEvent* e) override; + +private: + void Retranslate(); + + enum ItemType { + kItemTypeNode, + kItemTypeInput + }; + + static const int kItemType = Qt::UserRole; + static const int kItemPointer = Qt::UserRole + 1; + + QList nodes_; + + QList disabled_nodes_; + + QList disabled_inputs_; + + bool only_show_keyframable_; + +private slots: + void ItemCheckStateChanged(QTreeWidgetItem* item, int column); + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // NODETREEVIEW_H diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index cbb4b48da..983dcdba4 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -72,6 +72,7 @@ MainWindow::MainWindow(QWidget *parent) : node_panel_ = PanelManager::instance()->CreatePanel(this); footage_viewer_panel_ = PanelManager::instance()->CreatePanel(this); param_panel_ = PanelManager::instance()->CreatePanel(this); + curve_panel_ = PanelManager::instance()->CreatePanel(this); table_panel_ = PanelManager::instance()->CreatePanel(this); sequence_viewer_panel_ = PanelManager::instance()->CreatePanel(this); pixel_sampler_panel_ = PanelManager::instance()->CreatePanel(this); @@ -87,14 +88,25 @@ MainWindow::MainWindow(QWidget *parent) : connect(node_panel_, &NodePanel::NodesSelected, table_panel_, &NodeTablePanel::SelectNodes); connect(node_panel_, &NodePanel::NodesDeselected, table_panel_, &NodeTablePanel::DeselectNodes); connect(param_panel_, &ParamPanel::RequestSelectNode, node_panel_, &NodePanel::Select); + + // Connect time signals together connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTimestamp); connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, table_panel_, &NodeTablePanel::SetTimestamp); + connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, curve_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(param_panel_, &ParamPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTimestamp); + connect(curve_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTimestamp); + connect(curve_panel_, &ParamPanel::TimeChanged, table_panel_, &NodeTablePanel::SetTimestamp); + connect(curve_panel_, &ParamPanel::TimeChanged, param_panel_, &NodeTablePanel::SetTimestamp); + + // Connect node order signals + connect(param_panel_, &ParamPanel::NodeOrderChanged, curve_panel_, &CurvePanel::SetNodes); + connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged); sequence_viewer_panel_->ConnectTimeBasedPanel(param_panel_); + sequence_viewer_panel_->ConnectTimeBasedPanel(curve_panel_); footage_viewer_panel_->ConnectPixelSamplerPanel(pixel_sampler_panel_); sequence_viewer_panel_->ConnectPixelSamplerPanel(pixel_sampler_panel_); @@ -240,26 +252,6 @@ ScopePanel *MainWindow::AppendScopePanel() return AppendFloatingPanelInternal(scope_panels_); } -CurvePanel *MainWindow::AppendCurvePanel() -{ - CurvePanel* p = AppendFloatingPanelInternal(curve_panels_); - - sequence_viewer_panel_->ConnectTimeBasedPanel(p); - - return p; - - /*connect(panel, &TimelinePanel::TimeChanged, curve_panel_, &CurvePanel::SetTime); - connect(curve_panel_, &CurvePanel::TimeChanged, panel, &TimelinePanel::SetTime); - connect(param_panel_, &ParamPanel::SelectedInputChanged, curve_panel_, &CurvePanel::SetInput); - connect(param_panel_, &ParamPanel::TimebaseChanged, curve_panel_, &CurvePanel::SetTimebase); - connect(param_panel_, &ParamPanel::TimeTargetChanged, curve_panel_, &CurvePanel::SetTimeTarget); - connect(param_panel_, &ParamPanel::TimeChanged, curve_panel_, &CurvePanel::SetTime); - connect(curve_panel_, &CurvePanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); - connect(curve_panel_, &CurvePanel::TimeChanged, param_panel_, &ParamPanel::SetTime); - sequence_viewer_panel_->ConnectTimeBasedPanel(curve_panel_); - connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, curve_panel_, &CurvePanel::SetTime);*/ -} - void MainWindow::SetFullscreen(bool fullscreen) { if (fullscreen) { @@ -495,12 +487,14 @@ TimelinePanel* MainWindow::AppendTimelinePanel() TimelinePanel* panel = AppendPanelInternal(timeline_panels_); connect(panel, &PanelWidget::CloseRequested, this, &MainWindow::TimelineCloseRequested); + connect(panel, &TimelinePanel::TimeChanged, curve_panel_, &ParamPanel::SetTimestamp); 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::BlocksSelected, node_panel_, &NodePanel::SelectBlocks); connect(panel, &TimelinePanel::BlocksDeselected, node_panel_, &NodePanel::DeselectBlocks); connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp); + connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp); connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp); sequence_viewer_panel_->ConnectTimeBasedPanel(panel); @@ -546,6 +540,7 @@ void MainWindow::TimelineFocused(ViewerOutput* viewer) { sequence_viewer_panel_->ConnectViewerNode(viewer); param_panel_->ConnectViewerNode(viewer); + curve_panel_->ConnectViewerNode(viewer); Sequence* seq = nullptr; @@ -585,6 +580,10 @@ void MainWindow::SetDefaultLayout() tabifyDockWidget(footage_viewer_panel_, param_panel_); footage_viewer_panel_->raise(); + curve_panel_->hide(); + curve_panel_->setFloating(true); + addDockWidget(Qt::TopDockWidgetArea, curve_panel_); + table_panel_->hide(); table_panel_->setFloating(true); addDockWidget(Qt::TopDockWidgetArea, table_panel_); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 923a8f1b6..6846c469e 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -70,8 +70,6 @@ public: ScopePanel* AppendScopePanel(); - CurvePanel* AppendCurvePanel(); - enum ProgressStatus { kProgressNone, kProgressShow, @@ -138,6 +136,7 @@ private: // Standard panels NodePanel* node_panel_; ParamPanel* param_panel_; + CurvePanel* curve_panel_; SequenceViewerPanel* sequence_viewer_panel_; FootageViewerPanel* footage_viewer_panel_; QList project_panels_; @@ -146,7 +145,6 @@ private: QList timeline_panels_; AudioMonitorPanel* audio_monitor_panel_; TaskManagerPanel* task_man_panel_; - QList curve_panels_; PixelSamplerPanel* pixel_sampler_panel_; QList scope_panels_; NodeTablePanel* table_panel_; From 92db73502229cf0cb1fd36d293893ec008512662 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 00:06:38 +0100 Subject: [PATCH 17/74] Add Delete functionality. Still buggy. --- app/project/projectviewmodel.cpp | 33 +++++++++++++++++++ app/project/projectviewmodel.h | 24 ++++++++++++++ .../projectexplorer/projectexplorer.cpp | 19 +++++++++++ 3 files changed, 76 insertions(+) diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index 8c176f24f..5b90fde71 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -26,6 +26,7 @@ #include "core.h" #include "node/input/media/media.h" +#include "widget/timelinewidget/timelinewidget.h" OLIVE_NAMESPACE_ENTER @@ -641,4 +642,36 @@ void ProjectViewModel::OfflineFootageCommand::undo_internal() } } +ProjectViewModel::DeleteFootageCommand::DeleteFootageCommand(ProjectViewModel* model, ItemPtr item, + QList blocks, QUndoCommand* parent) : + UndoCommand(parent), + model_(model), + item_(item), + blocks_(blocks) +{ + deleteCommand_ = new QUndoCommand(); + removalCommand_ = new QUndoCommand(); +} + +Project *ProjectViewModel::DeleteFootageCommand::GetRelevantProject() const +{ + return model_->project(); +} + +void ProjectViewModel::DeleteFootageCommand::redo_internal() +{ + TimelineWidget::ReplaceBlocksWithGaps(blocks_, true, deleteCommand_); + Core::instance()->undo_stack()->pushIfHasChildren(deleteCommand_); + + new ProjectViewModel::RemoveItemCommand(model_, item_, removalCommand_); + Core::instance()->undo_stack()->pushIfHasChildren(removalCommand_); +} + +void ProjectViewModel::DeleteFootageCommand::undo_internal() +{ + removalCommand_->undo(); + deleteCommand_->undo(); +} + + OLIVE_NAMESPACE_EXIT diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index d79542aa0..16e56fffa 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -25,6 +25,7 @@ #include "project.h" #include "undo/undocommand.h" +#include "node/block/block.h" OLIVE_NAMESPACE_ENTER @@ -215,6 +216,29 @@ public: QMap nodes_; }; + class DeleteFootageCommand : public UndoCommand { + public: + DeleteFootageCommand(ProjectViewModel* model, ItemPtr item, QList blocks, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo_internal() override; + + virtual void undo_internal() override; + + private: + ProjectViewModel* model_; + + ItemPtr item_; + + QList blocks_; + + QUndoCommand* deleteCommand_; + + QUndoCommand* removalCommand_; + }; + private: /** * @brief Retrieve the index of `item` in its parent diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index e0ca1aff2..dfdaa7228 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -37,6 +37,7 @@ #include "widget/menu/menu.h" #include "widget/menu/menushared.h" #include "window/mainwindow/mainwindow.h" +#include "widget/timelinewidget/timelinewidget.h" OLIVE_NAMESPACE_ENTER @@ -633,6 +634,24 @@ void ProjectExplorer::DeleteSelected() // Get all sequences. QList sequences = model_.project()->get_items_of_type(Item::kSequence); + QList blocks; + + foreach(ItemPtr seq, sequences) { + Sequence* s = static_cast(seq.get()); + // Loop through nodes in sequence + foreach(Node* node, s->nodes()) { + + // For each Block see if it is linked to one of the Footage nodes add it to the delete list + if (node->IsBlock()) { + foreach(Node* input, nodes.keys()) { + if(node->GetExclusiveDependencies().contains(input)) + blocks.append(static_cast(node)); + } + } + } + } + new ProjectViewModel::DeleteFootageCommand(&model_, item_ptr, blocks, command); + } if (response == kCancel) { delete command; From fec9774935004551c4a8434c0411cae0ce379c1f Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 29 Sep 2020 16:41:12 +1000 Subject: [PATCH 18/74] curveview: finished rework --- app/widget/curvewidget/curveview.cpp | 192 +++++++------------ app/widget/curvewidget/curveview.h | 12 +- app/widget/keyframeview/keyframeviewbase.cpp | 25 --- app/widget/keyframeview/keyframeviewbase.h | 4 - app/widget/nodeparamview/nodeparamview.cpp | 2 +- 5 files changed, 71 insertions(+), 164 deletions(-) diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index 375d9604a..a9b43d4a9 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -60,21 +60,6 @@ void CurveView::Clear() lines_.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::ConnectInput(NodeInput *input) { if (connected_inputs_.contains(input)) { @@ -87,6 +72,11 @@ void CurveView::ConnectInput(NodeInput *input) foreach (NodeKeyframePtr key, track) { this->AddKeyframe(key); } + + if (!keyframe_colors_.contains(&track)) { + // Generate a random color for this input + keyframe_colors_.insert(&track, QColor::fromHsv(std::rand()%360, std::rand()%255, 255)); + } } // Append to the list @@ -175,87 +165,85 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) painter->drawLines(lines); // Draw keyframe lines + foreach (NodeInput* input, connected_inputs_) { + if (input->is_keyframing()) { + foreach (const NodeInput::KeyframeTrack& track, input->keyframe_tracks()) { + if (!track.isEmpty()) { + painter->setPen(QPen(keyframe_colors_.value(&track), qMax(1, fontMetrics().height() / 4))); - for (int j=0;j keyframe_lines; - painter->setPen(QPen(GetKeyframeColor(j), qMax(1, fontMetrics().height() / 4))); - QList keys = GetKeyframesSortedByTime(j); + // Draw straight line leading to first keyframe + QPointF first_key_pos = item_map().value(track.first().get())->pos(); + keyframe_lines.append(QLineF(QPointF(scene_bottom_left.x(), first_key_pos.y()), first_key_pos)); - if (!keys.isEmpty()) { - QVector keyframe_lines; + // Draw lines between each keyframe + for (int i=1;ipos(); - keyframe_lines.append(QLineF(QPointF(scene_bottom_left.x(), first_key_pos.y()), first_key_pos)); + KeyframeViewItem* before_item = item_map().value(before.get()); + KeyframeViewItem* after_item = item_map().value(after.get()); - // Draw lines between each keyframe - for (int i=1;itype() == NodeKeyframe::kHold) { + // Draw a hold keyframe (basically a right angle) + keyframe_lines.append(QLineF(before_item->pos().x(), + before_item->pos().y(), + after_item->pos().x(), + before_item->pos().y())); + keyframe_lines.append(QLineF(after_item->pos().x(), + before_item->pos().y(), + after_item->pos().x(), + after_item->pos().y())); + } else if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) { + // Draw a cubic bezier - KeyframeViewItem* before_item = item_map().value(before); - KeyframeViewItem* after_item = item_map().value(after); + // Cubic beziers have two control points, so we can just use both + QPointF before_control_point = before_item->pos() + ScalePoint(before->bezier_control_out()); + QPointF after_control_point = after_item->pos() + ScalePoint(after->bezier_control_in()); - if (before->type() == NodeKeyframe::kHold) { - // Draw a hold keyframe (basically a right angle) - keyframe_lines.append(QLineF(before_item->pos().x(), - before_item->pos().y(), - after_item->pos().x(), - before_item->pos().y())); - keyframe_lines.append(QLineF(after_item->pos().x(), - before_item->pos().y(), - after_item->pos().x(), - after_item->pos().y())); - } else if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) { - // Draw a cubic bezier + QPainterPath path; + path.moveTo(before_item->pos()); + path.cubicTo(before_control_point, after_control_point, after_item->pos()); + painter->drawPath(path); - // Cubic beziers have two control points, so we can just use both - QPointF before_control_point = before_item->pos() + ScalePoint(before->bezier_control_out()); - QPointF after_control_point = after_item->pos() + ScalePoint(after->bezier_control_in()); + } else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) { + // Draw a quadratic bezier - QPainterPath path; - path.moveTo(before_item->pos()); - path.cubicTo(before_control_point, after_control_point, after_item->pos()); - painter->drawPath(path); + // Quadratic beziers have a single control point, we just have to determine which it is + QPointF key_anchor; + QPointF control_point; - } else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) { - // Draw a quadratic bezier + if (before->type() == NodeKeyframe::kBezier) { + key_anchor = before_item->pos(); + control_point = before->bezier_control_out(); + } else { + key_anchor = after_item->pos(); + control_point = after->bezier_control_in(); + } - // Quadratic beziers have a single control point, we just have to determine which it is - QPointF key_anchor; - QPointF control_point; + // Scale control point + control_point = key_anchor + ScalePoint(control_point); - if (before->type() == NodeKeyframe::kBezier) { - key_anchor = before_item->pos(); - control_point = before->bezier_control_out(); - } else { - key_anchor = after_item->pos(); - control_point = after->bezier_control_in(); + // Create the path from both keyframes + QPainterPath path; + path.moveTo(before_item->pos()); + path.quadTo(control_point, after_item->pos()); + painter->drawPath(path); + + } else { + // Linear to linear + keyframe_lines.append(QLineF(before_item->pos(), after_item->pos())); + } } - // Scale control point - control_point = key_anchor + ScalePoint(control_point); + // Draw straight line leading from end keyframe + QPointF last_key_pos = item_map().value(track.last().get())->pos(); + keyframe_lines.append(QLineF(last_key_pos, QPointF(scene_top_right.x(), last_key_pos.y()))); - // Create the path from both keyframes - QPainterPath path; - path.moveTo(before_item->pos()); - path.quadTo(control_point, after_item->pos()); - painter->drawPath(path); - - } else { - // Linear to linear - keyframe_lines.append(QLineF(before_item->pos(), after_item->pos())); + painter->drawLines(keyframe_lines); } } - - // Draw straight line leading from end keyframe - QPointF last_key_pos = item_map().value(keys.last())->pos(); - keyframe_lines.append(QLineF(last_key_pos, QPointF(scene_top_right.x(), last_key_pos.y()))); - - painter->drawLines(keyframe_lines); } } @@ -318,35 +306,6 @@ void CurveView::ContextMenuEvent(Menu &m) //QAction* reset_zoom_action = m.addAction(tr("Reset Zoom")); } -QList CurveView::GetKeyframesSortedByTime(int track) -{ - QList sorted; - - for (auto it=item_map().cbegin();it!=item_map().cend();it++) { - NodeKeyframe* key = it.key(); - - if (key->track() != track) { - continue; - } - - bool inserted = false; - - for (int i=0;itime() > key->time()) { - sorted.insert(i, key); - inserted = true; - break; - } - } - - if (!inserted) { - sorted.append(key); - } - } - - return sorted; -} - qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key) { return GetItemYFromKeyframeValue(key->value().toDouble()); @@ -381,17 +340,6 @@ void CurveView::CreateBezierControlPoints(KeyframeViewItem* item) connect(bezier_out_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection); } -QColor CurveView::GetKeyframeColor(int track) const -{ - if (track_count_) { - QColor c; - c.setHsvF(static_cast(track) / static_cast(track_count_), 0.5, 1.0); - return c; - } - - return palette().text().color(); -} - void CurveView::KeyframeValueChanged() { NodeKeyframe* key = static_cast(sender()); @@ -443,15 +391,13 @@ void CurveView::ZoomToFit() 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++) { + for (auto i=item_map().constBegin(); i!=item_map().constEnd(); i++) { rational transformed_time = GetAdjustedTime(i.key()->parent()->parentNode(), GetTimeTarget(), i.key()->time(), @@ -479,7 +425,7 @@ void CurveView::AddKeyframe(NodeKeyframePtr key) { KeyframeViewItem* item = AddKeyframeInternal(key); SetItemYFromKeyframeValue(key.get(), item); - item->SetOverrideBrush(GetKeyframeColor(key->track())); + item->SetOverrideBrush(keyframe_colors_.value(&key->parent()->keyframe_tracks().at(key->track()))); connect(key.get(), &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged); connect(key.get(), &NodeKeyframe::TypeChanged, this, &CurveView::KeyframeTypeChanged); diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index a5a9dce8c..762a015c5 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -38,10 +38,6 @@ public: virtual void Clear() override; - void SetTrackCount(int count); - - void SetTrackVisible(int track, bool visible); - void ConnectInput(NodeInput* input); void DisconnectNode(Node* node); @@ -67,8 +63,6 @@ protected: virtual void ContextMenuEvent(Menu &m) override; private: - QList GetKeyframesSortedByTime(int track); - qreal GetItemYFromKeyframeValue(NodeKeyframe* key); qreal GetItemYFromKeyframeValue(double value); @@ -80,7 +74,7 @@ private: void CreateBezierControlPoints(KeyframeViewItem *item); - QColor GetKeyframeColor(int track) const; + QMap keyframe_colors_; int text_padding_; @@ -90,12 +84,8 @@ private: QList bezier_control_points_; - QVector track_visible_; - QList connected_inputs_; - int track_count_; - private slots: void KeyframeValueChanged(); diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index c114faf81..b53a0c236 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -109,10 +109,6 @@ 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,27 +302,6 @@ void KeyframeViewBase::TimeTargetChangedEvent(Node *target) } } -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); - } -} - void KeyframeViewBase::ContextMenuEvent(Menu& m) { Q_UNUSED(m) diff --git a/app/widget/keyframeview/keyframeviewbase.h b/app/widget/keyframeview/keyframeviewbase.h index c6d40fbd8..f25a66937 100644 --- a/app/widget/keyframeview/keyframeviewbase.h +++ b/app/widget/keyframeview/keyframeviewbase.h @@ -62,8 +62,6 @@ protected: virtual void TimeTargetChangedEvent(Node*) override; - void SetKeyframeTrackVisible(int track, bool visible); - virtual void ContextMenuEvent(Menu &m); private: @@ -98,8 +96,6 @@ private: bool currently_autoselecting_; - QList hidden_tracks_; - private slots: void ShowContextMenu(); diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 389a1db97..159c8d64d 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -308,7 +308,7 @@ void NodeParamView::ItemRequestedTimeChanged(const rational &time) void NodeParamView::UpdateGlobalScrollBar() { - int height_offscreen = param_widget_container_->height() - ruler()->height(); + int height_offscreen = param_widget_container_->height() - ruler()->height() + scrollbar()->height(); keyframe_view_->SetMaxScroll(height_offscreen); vertical_scrollbar_->setRange(0, height_offscreen - keyframe_view_->height()); From aef907859880cb9517d838c034475da5947558f2 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 10:56:30 +0100 Subject: [PATCH 19/74] Fix delete redo code --- app/project/projectviewmodel.cpp | 30 ++++++++++++++----- app/project/projectviewmodel.h | 9 ++++-- .../projectexplorer/projectexplorer.cpp | 6 +++- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index 5b90fde71..0947a6635 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -642,15 +642,15 @@ void ProjectViewModel::OfflineFootageCommand::undo_internal() } } -ProjectViewModel::DeleteFootageCommand::DeleteFootageCommand(ProjectViewModel* model, ItemPtr item, - QList blocks, QUndoCommand* parent) : +ProjectViewModel::DeleteFootageCommand::DeleteFootageCommand(ProjectViewModel *model, ItemPtr item, + QMap nodes, QUndoCommand *parent) + : UndoCommand(parent), model_(model), item_(item), - blocks_(blocks) + nodes_(nodes) { deleteCommand_ = new QUndoCommand(); - removalCommand_ = new QUndoCommand(); } Project *ProjectViewModel::DeleteFootageCommand::GetRelevantProject() const @@ -660,16 +660,32 @@ Project *ProjectViewModel::DeleteFootageCommand::GetRelevantProject() const void ProjectViewModel::DeleteFootageCommand::redo_internal() { + QList sequences = model_->project()->get_items_of_type(Item::kSequence); + + blocks_.clear(); + + foreach (ItemPtr seq, sequences) { + Sequence *s = static_cast(seq.get()); + // Loop through nodes in sequence + foreach (Node *node, s->nodes()) { + // For each Block see if it is linked to one of the Footage nodes add it to the delete list + if (node->IsBlock()) { + foreach (Node *input, nodes_.keys()) { + if (node->GetExclusiveDependencies().contains(input)) blocks_.append(static_cast(node)); + } + } + } + } TimelineWidget::ReplaceBlocksWithGaps(blocks_, true, deleteCommand_); Core::instance()->undo_stack()->pushIfHasChildren(deleteCommand_); - new ProjectViewModel::RemoveItemCommand(model_, item_, removalCommand_); - Core::instance()->undo_stack()->pushIfHasChildren(removalCommand_); + parent_ = item_->parent(); + model_->RemoveChild(parent_, item_.get()); } void ProjectViewModel::DeleteFootageCommand::undo_internal() { - removalCommand_->undo(); + model_->AddChild(parent_, item_); deleteCommand_->undo(); } diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index 16e56fffa..05c2671c8 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -218,7 +218,8 @@ public: class DeleteFootageCommand : public UndoCommand { public: - DeleteFootageCommand(ProjectViewModel* model, ItemPtr item, QList blocks, QUndoCommand* parent = nullptr); + DeleteFootageCommand(ProjectViewModel* model, ItemPtr item, QMap nodes, + QUndoCommand* parent = nullptr); virtual Project* GetRelevantProject() const override; @@ -232,11 +233,13 @@ public: ItemPtr item_; + Item* parent_; + + QMap nodes_; + QList blocks_; QUndoCommand* deleteCommand_; - - QUndoCommand* removalCommand_; }; private: diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index dfdaa7228..e666ddf1d 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -650,7 +650,11 @@ void ProjectExplorer::DeleteSelected() } } } - new ProjectViewModel::DeleteFootageCommand(&model_, item_ptr, blocks, command); + //new ProjectViewModel::DeleteFootageCommand(&model_, item_ptr, blocks, command); + + QUndoCommand* deleteCommand = new QUndoCommand(command); + TimelineWidget::ReplaceBlocksWithGaps(blocks, true, deleteCommand); + //Core::instance()->undo_stack()->pushIfHasChildren(deleteCommand); } if (response == kCancel) { From 8e6b42a160408111c0ddb15c119d99c2b1209356 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 11:31:03 +0100 Subject: [PATCH 20/74] Simplify and clean up Remove the Undo code for deleting a clip and replace it with simpler parent/child undo relationship. Move block gathering code to seperate function. It's quite brute force and might lead to issues further down the line with more complex composited node setups. --- app/project/projectviewmodel.cpp | 48 ------------------ app/project/projectviewmodel.h | 26 ---------- .../projectexplorer/projectexplorer.cpp | 50 ++++++++++--------- app/widget/projectexplorer/projectexplorer.h | 5 ++ 4 files changed, 32 insertions(+), 97 deletions(-) diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index 0947a6635..3a94252d6 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -642,52 +642,4 @@ void ProjectViewModel::OfflineFootageCommand::undo_internal() } } -ProjectViewModel::DeleteFootageCommand::DeleteFootageCommand(ProjectViewModel *model, ItemPtr item, - QMap nodes, QUndoCommand *parent) - : - UndoCommand(parent), - model_(model), - item_(item), - nodes_(nodes) -{ - deleteCommand_ = new QUndoCommand(); -} - -Project *ProjectViewModel::DeleteFootageCommand::GetRelevantProject() const -{ - return model_->project(); -} - -void ProjectViewModel::DeleteFootageCommand::redo_internal() -{ - QList sequences = model_->project()->get_items_of_type(Item::kSequence); - - blocks_.clear(); - - foreach (ItemPtr seq, sequences) { - Sequence *s = static_cast(seq.get()); - // Loop through nodes in sequence - foreach (Node *node, s->nodes()) { - // For each Block see if it is linked to one of the Footage nodes add it to the delete list - if (node->IsBlock()) { - foreach (Node *input, nodes_.keys()) { - if (node->GetExclusiveDependencies().contains(input)) blocks_.append(static_cast(node)); - } - } - } - } - TimelineWidget::ReplaceBlocksWithGaps(blocks_, true, deleteCommand_); - Core::instance()->undo_stack()->pushIfHasChildren(deleteCommand_); - - parent_ = item_->parent(); - model_->RemoveChild(parent_, item_.get()); -} - -void ProjectViewModel::DeleteFootageCommand::undo_internal() -{ - model_->AddChild(parent_, item_); - deleteCommand_->undo(); -} - - OLIVE_NAMESPACE_EXIT diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index 05c2671c8..bd3e068ed 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -216,32 +216,6 @@ public: QMap nodes_; }; - class DeleteFootageCommand : public UndoCommand { - public: - DeleteFootageCommand(ProjectViewModel* model, ItemPtr item, QMap nodes, - QUndoCommand* parent = nullptr); - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo_internal() override; - - virtual void undo_internal() override; - - private: - ProjectViewModel* model_; - - ItemPtr item_; - - Item* parent_; - - QMap nodes_; - - QList blocks_; - - QUndoCommand* deleteCommand_; - }; - private: /** * @brief Retrieve the index of `item` in its parent diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index e666ddf1d..26fedcd2d 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -574,6 +574,31 @@ QMap ProjectExplorer::GetFootageNodes(Item* item) return nodes; } +QList ProjectExplorer::GetFootageBlocks(QList nodes) +{ + // Get all sequences. + QList sequences = model_.project()->get_items_of_type(Item::kSequence); + + QList blocks; + + foreach (ItemPtr seq, sequences) { + Sequence* s = static_cast(seq.get()); + // Loop through nodes in sequence + foreach (Node* node, s->nodes()) { + // For each Block see if it is linked to one of the Footage nodes add it to the delete list + if (node->IsBlock()) { + foreach (Node* input, nodes) { + if (node->GetExclusiveDependencies().contains(input)) { + blocks.append(static_cast(node)); + } + } + } + } + } + + return blocks; +} + ProjectExplorer::FootageDeleteResponse ProjectExplorer::DeleteWarningMessage() { QMessageBox msgBox; @@ -631,31 +656,10 @@ void ProjectExplorer::DeleteSelected() new ProjectViewModel::OfflineFootageCommand(&model_, item_ptr, nodes, command); } if (response == kDelete) { - // Get all sequences. - QList sequences = model_.project()->get_items_of_type(Item::kSequence); - - QList blocks; - - foreach(ItemPtr seq, sequences) { - Sequence* s = static_cast(seq.get()); - // Loop through nodes in sequence - foreach(Node* node, s->nodes()) { - - // For each Block see if it is linked to one of the Footage nodes add it to the delete list - if (node->IsBlock()) { - foreach(Node* input, nodes.keys()) { - if(node->GetExclusiveDependencies().contains(input)) - blocks.append(static_cast(node)); - } - } - } - } - //new ProjectViewModel::DeleteFootageCommand(&model_, item_ptr, blocks, command); - QUndoCommand* deleteCommand = new QUndoCommand(command); - TimelineWidget::ReplaceBlocksWithGaps(blocks, true, deleteCommand); - //Core::instance()->undo_stack()->pushIfHasChildren(deleteCommand); + TimelineWidget::ReplaceBlocksWithGaps(GetFootageBlocks(nodes.keys()), true, deleteCommand); + new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); } if (response == kCancel) { delete command; diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 907da2964..8900fc9de 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -122,6 +122,11 @@ private: */ QMap GetFootageNodes(Item* item); + /** + * @brief Get all the blocks associated with the given footage nodes + */ + QList GetFootageBlocks(QList nodes); + /** * @brief Simple convenience function for adding a view to this stacked widget * From 791857fba1dcdce2860029fde56d2fd309149249 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 12:03:01 +0100 Subject: [PATCH 21/74] Cleanup. --- app/project/projectviewmodel.cpp | 1 - app/project/projectviewmodel.h | 3 +++ app/widget/projectexplorer/projectexplorer.cpp | 2 +- app/widget/projectexplorer/projectexplorer.h | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index 3a94252d6..8c176f24f 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -26,7 +26,6 @@ #include "core.h" #include "node/input/media/media.h" -#include "widget/timelinewidget/timelinewidget.h" OLIVE_NAMESPACE_ENTER diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index bd3e068ed..8da1c2472 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -195,6 +195,9 @@ public: }; + /** + * @brief An undo command for offlining footage when it is deleted from the project explorer + */ class OfflineFootageCommand : public UndoCommand { public: OfflineFootageCommand(ProjectViewModel* model, ItemPtr item, QMap nodes, QUndoCommand* parent = nullptr); diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 26fedcd2d..fecb0ac6f 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -588,7 +588,7 @@ QList ProjectExplorer::GetFootageBlocks(QList nodes) // For each Block see if it is linked to one of the Footage nodes add it to the delete list if (node->IsBlock()) { foreach (Node* input, nodes) { - if (node->GetExclusiveDependencies().contains(input)) { + if (node->GetDependencies().contains(input)) { blocks.append(static_cast(node)); } } diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 8900fc9de..a91af4cd2 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -124,6 +124,8 @@ private: /** * @brief Get all the blocks associated with the given footage nodes + * + * Currently quite brute force. */ QList GetFootageBlocks(QList nodes); From a71df11fa657b373a404b5907550d82326d6dc07 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 13:29:59 +0100 Subject: [PATCH 22/74] Deal with composite blocks If a block relies on mulitple inputs (i.e. a composite) then don't delete these from the timeline. We catch these inputs afterwards. --- .../projectexplorer/projectexplorer.cpp | 41 ++++++++++++++++--- app/widget/projectexplorer/projectexplorer.h | 6 +-- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index fecb0ac6f..54b27d5da 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -38,6 +38,7 @@ #include "widget/menu/menushared.h" #include "window/mainwindow/mainwindow.h" #include "widget/timelinewidget/timelinewidget.h" +#include "widget/nodeview/nodeviewundo.h" OLIVE_NAMESPACE_ENTER @@ -585,13 +586,23 @@ QList ProjectExplorer::GetFootageBlocks(QList nodes) Sequence* s = static_cast(seq.get()); // Loop through nodes in sequence foreach (Node* node, s->nodes()) { - // For each Block see if it is linked to one of the Footage nodes add it to the delete list + // For each Block see if it solely depends on one of our input nodes and if so add to the block list if (node->IsBlock()) { - foreach (Node* input, nodes) { - if (node->GetDependencies().contains(input)) { - blocks.append(static_cast(node)); + int footage_deps = 0; + + QList dependancies = node->GetDependencies(); + QSet intersection = QSet(dependancies.begin(), dependancies.end()) + .intersect(QSet(nodes.begin(), nodes.end())); + if (!intersection.isEmpty()) { + foreach (Node* dep, dependancies) { + if (dep->IsMedia()) { + footage_deps++; + } + } + if (footage_deps == 1) { + blocks.append(static_cast(node)); + } } - } } } } @@ -658,8 +669,26 @@ void ProjectExplorer::DeleteSelected() if (response == kDelete) { QUndoCommand* deleteCommand = new QUndoCommand(command); TimelineWidget::ReplaceBlocksWithGaps(GetFootageBlocks(nodes.keys()), true, deleteCommand); - new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); + //Core::instance()->undo_stack()->pushIfHasChildren(command); + + // Catch any input nodes we missed do to complex composites etc. + + QList sequences = model_.project()->get_items_of_type(Item::kSequence); + + QList nodes_to_delete; + foreach (ItemPtr seq, sequences) { + Sequence* s = static_cast(seq.get()); + foreach (Node* node, s->nodes()) { + if (node->IsMedia()) { + if (nodes.contains(node)) { + nodes_to_delete.append(node); + } + } + } + QUndoCommand* deleteNodesCommand = new QUndoCommand(deleteCommand); + new NodeRemoveCommand(static_cast(s), nodes_to_delete, deleteNodesCommand); + } } if (response == kCancel) { delete command; diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index a91af4cd2..d393959fa 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -123,9 +123,9 @@ private: QMap GetFootageNodes(Item* item); /** - * @brief Get all the blocks associated with the given footage nodes - * - * Currently quite brute force. + * @brief Get all the blocks that solely rely on an input node + * + * Ignores blocks that depend on multiple inputs */ QList GetFootageBlocks(QList nodes); From 56e2b510417e11f649bdcfc5aae317e5410ea622 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 14:03:25 +0100 Subject: [PATCH 23/74] Add filename to warning message. --- .../projectexplorer/projectexplorer.cpp | 36 +++++++++++-------- app/widget/projectexplorer/projectexplorer.h | 2 +- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 54b27d5da..2294ea413 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -610,24 +610,30 @@ QList ProjectExplorer::GetFootageBlocks(QList nodes) return blocks; } -ProjectExplorer::FootageDeleteResponse ProjectExplorer::DeleteWarningMessage() +ProjectExplorer::FootageDeleteResponse ProjectExplorer::DeleteWarningMessage(Item* item) { - QMessageBox msgBox; - msgBox.setText(tr("This footage is in use.")); - QPushButton* offline = msgBox.addButton(tr("Offline Footage"), QMessageBox::ApplyRole); - QPushButton* deleteClips = msgBox.addButton(tr("Delete Clips"), QMessageBox::ApplyRole); - msgBox.setStandardButtons(QMessageBox::Cancel); - msgBox.setIcon(QMessageBox::Warning); + ItemPtr item_ptr = item->get_shared_ptr(); - msgBox.exec(); + if (item_ptr->type() == Item::kFootage) { + QString clip_name = static_cast(item_ptr.get())->filename().split("/").last(); - if (msgBox.clickedButton() == offline) { - return kOffline; + QMessageBox msgBox; + msgBox.setWindowTitle(clip_name); + msgBox.setText(clip_name + tr(" is in use.")); + QPushButton* offline = msgBox.addButton(tr("Offline Footage"), QMessageBox::ApplyRole); + QPushButton* deleteClips = msgBox.addButton(tr("Delete Clips"), QMessageBox::ApplyRole); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setIcon(QMessageBox::Warning); + + msgBox.exec(); + + if (msgBox.clickedButton() == offline) { + return kOffline; + } + if (msgBox.clickedButton() == deleteClips) { + return kDelete; + } } - if (msgBox.clickedButton() == deleteClips) { - return kDelete; - } - return kCancel; } @@ -662,7 +668,7 @@ void ProjectExplorer::DeleteSelected() QMap nodes = GetFootageNodes(item); if (!nodes.isEmpty()){ // Warn user and ask them what to do - FootageDeleteResponse response = DeleteWarningMessage(); + FootageDeleteResponse response = DeleteWarningMessage(item); if (response == kOffline) { new ProjectViewModel::OfflineFootageCommand(&model_, item_ptr, nodes, command); } diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index d393959fa..daca3aec1 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -113,7 +113,7 @@ private: * * Returns a FootageDeleteResponse */ - FootageDeleteResponse DeleteWarningMessage(); + FootageDeleteResponse DeleteWarningMessage(Item* item); /** * @brief Check if an item is in use anywhere and return any relevant input nodes From 7704e004430b34b506654d39874034e49d61ec49 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 14:14:21 +0100 Subject: [PATCH 24/74] Cleanup --- .../projectexplorer/projectexplorer.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 2294ea413..b63a40919 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -542,7 +542,7 @@ void ProjectExplorer::DeselectAll() QMap ProjectExplorer::GetFootageNodes(Item* item) { // Output list list - QMap nodes; + QMap footage_nodes; // Get all sequences. QList sequences = model_.project()->get_items_of_type(Item::kSequence); @@ -564,7 +564,7 @@ QMap ProjectExplorer::GetFootageNodes(Item* item) if (node->IsMedia()){ // Check the streams are the same if (static_cast(node)->footage() == stream) { - nodes.insert(node, stream); + footage_nodes.insert(node, stream); } } } @@ -572,7 +572,7 @@ QMap ProjectExplorer::GetFootageNodes(Item* item) } } } - return nodes; + return footage_nodes; } QList ProjectExplorer::GetFootageBlocks(QList nodes) @@ -594,11 +594,13 @@ QList ProjectExplorer::GetFootageBlocks(QList nodes) QSet intersection = QSet(dependancies.begin(), dependancies.end()) .intersect(QSet(nodes.begin(), nodes.end())); if (!intersection.isEmpty()) { + // Count how many Media inputs this block depends on foreach (Node* dep, dependancies) { if (dep->IsMedia()) { footage_deps++; } } + // If it only depends on one input we can safely delete it if (footage_deps == 1) { blocks.append(static_cast(node)); } @@ -665,20 +667,20 @@ void ProjectExplorer::DeleteSelected() if (item_ptr->type() == Item::kFootage) { // Check if nodes exists - QMap nodes = GetFootageNodes(item); - if (!nodes.isEmpty()){ + QMap footage_nodes = GetFootageNodes(item); + if (!footage_nodes.isEmpty()){ // Warn user and ask them what to do FootageDeleteResponse response = DeleteWarningMessage(item); if (response == kOffline) { - new ProjectViewModel::OfflineFootageCommand(&model_, item_ptr, nodes, command); + new ProjectViewModel::OfflineFootageCommand(&model_, item_ptr, footage_nodes, command); } if (response == kDelete) { QUndoCommand* deleteCommand = new QUndoCommand(command); - TimelineWidget::ReplaceBlocksWithGaps(GetFootageBlocks(nodes.keys()), true, deleteCommand); + // Delete any non-composite blocks + TimelineWidget::ReplaceBlocksWithGaps(GetFootageBlocks(footage_nodes.keys()), true, deleteCommand); new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); - //Core::instance()->undo_stack()->pushIfHasChildren(command); - // Catch any input nodes we missed do to complex composites etc. + // Catch any input nodes we missed due to composites etc. QList sequences = model_.project()->get_items_of_type(Item::kSequence); @@ -687,7 +689,7 @@ void ProjectExplorer::DeleteSelected() Sequence* s = static_cast(seq.get()); foreach (Node* node, s->nodes()) { if (node->IsMedia()) { - if (nodes.contains(node)) { + if (footage_nodes.contains(node)) { nodes_to_delete.append(node); } } From b9359ce50d7b8292fcaaa233918ae0d526cfa29c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 29 Sep 2020 17:37:32 +1000 Subject: [PATCH 25/74] nodeparamview/viewer: improved gizmo selection system --- app/panel/param/param.cpp | 1 + app/panel/param/param.h | 2 + app/widget/nodeparamview/nodeparamview.cpp | 54 ++++++++++++++++++- app/widget/nodeparamview/nodeparamview.h | 6 +++ .../nodeparamview/nodeparamviewitem.cpp | 18 ++++++- app/widget/nodeparamview/nodeparamviewitem.h | 20 +++++-- app/window/mainwindow/mainwindow.cpp | 3 +- 7 files changed, 96 insertions(+), 8 deletions(-) diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index 5fa076690..2b9df7f4c 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -30,6 +30,7 @@ ParamPanel::ParamPanel(QWidget* parent) : NodeParamView* view = new NodeParamView(); connect(view, &NodeParamView::RequestSelectNode, this, &ParamPanel::RequestSelectNode); connect(view, &NodeParamView::NodeOrderChanged, this, &ParamPanel::NodeOrderChanged); + connect(view, &NodeParamView::FocusedNodeChanged, this, &ParamPanel::FocusedNodeChanged); SetTimeBasedWidget(view); Retranslate(); diff --git a/app/panel/param/param.h b/app/panel/param/param.h index f74bce63a..3c4f15354 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -44,6 +44,8 @@ signals: void NodeOrderChanged(const QList& nodes); + void FocusedNodeChanged(Node* n); + protected: virtual void Retranslate() override; diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 159c8d64d..b07031ea6 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -20,6 +20,7 @@ #include "nodeparamview.h" +#include #include #include #include @@ -31,7 +32,8 @@ OLIVE_NAMESPACE_ENTER NodeParamView::NodeParamView(QWidget *parent) : TimeBasedWidget(true, false, parent), - last_scroll_val_(0) + last_scroll_val_(0), + focused_node_(nullptr) { // Create horizontal layout to place scroll area in (and keyframe editing eventually) QHBoxLayout* layout = new QHBoxLayout(this); @@ -125,6 +127,12 @@ NodeParamView::NodeParamView(QWidget *parent) : SetScale(120); SetMaximumScale(TimelineViewBase::kMaximumScale); + + // Pickup on widget focus changes + connect(qApp, + &QApplication::focusChanged, + this, + &NodeParamView::FocusChanged); } void NodeParamView::SelectNodes(const QList &nodes) @@ -157,6 +165,13 @@ void NodeParamView::SelectNodes(const QList &nodes) param_widget_area_->addDockWidget(Qt::LeftDockWidgetArea, item); changes_made = true; + + if (!focused_node_ && n->HasGizmos()) { + // We'll focus this node now + item->SetHighlighted(true); + focused_node_ = n; + emit FocusedNodeChanged(focused_node_); + } } } @@ -299,6 +314,11 @@ void NodeParamView::RemoveNode(Node *n) keyframe_view_->RemoveKeyframesOfNode(n); delete items_.take(n); + + if (focused_node_ == n) { + focused_node_ = nullptr; + emit FocusedNodeChanged(nullptr); + } } void NodeParamView::ItemRequestedTimeChanged(const rational &time) @@ -337,4 +357,36 @@ void NodeParamView::PinNode(bool pin) } } +void NodeParamView::FocusChanged(QWidget* old, QWidget* now) +{ + Q_UNUSED(old) + + QObject* parent = now; + NodeParamViewItem* item; + + while (parent) { + item = dynamic_cast(parent); + + if (item) { + // Found it! + if (item->GetNode() != focused_node_) { + if (focused_node_) { + // De-focus current node + items_.value(focused_node_)->SetHighlighted(false); + } + + focused_node_ = item->GetNode(); + + item->SetHighlighted(true); + + emit FocusedNodeChanged(focused_node_); + } + + break; + } + + parent = parent->parent(); + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index a807b2171..f9fee45db 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -79,6 +79,8 @@ signals: void NodeOrderChanged(const QList& nodes); + void FocusedNodeChanged(Node* n); + protected: virtual void resizeEvent(QResizeEvent *event) override; @@ -117,6 +119,8 @@ private: QMap node_expanded_state_; + Node* focused_node_; + private slots: void ItemRequestedTimeChanged(const rational& time); @@ -126,6 +130,8 @@ private slots: void PinNode(bool pin); + void FocusChanged(QWidget *old, QWidget *now); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 6fcb95b91..0f5a88e67 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -33,7 +33,8 @@ OLIVE_NAMESPACE_ENTER NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : QDockWidget(parent), - node_(node) + node_(node), + highlighted_(false) { // Create title bar widget title_bar_ = new NodeParamViewItemTitleBar(this); @@ -75,6 +76,8 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + setFocusPolicy(Qt::ClickFocus); + Retranslate(); } @@ -109,6 +112,19 @@ void NodeParamViewItem::changeEvent(QEvent *e) QWidget::changeEvent(e); } +void NodeParamViewItem::paintEvent(QPaintEvent *event) +{ + QDockWidget::paintEvent(event); + + // Draw border if focused + if (highlighted_) { + QPainter p(this); + p.setBrush(Qt::NoBrush); + p.setPen(palette().highlight().color()); + p.drawRect(rect().adjusted(0, 0, -1, -1)); + } +} + void NodeParamViewItem::Retranslate() { node_->Retranslate(); diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index e97a150fd..2bc8972fa 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -138,9 +138,20 @@ public: bool IsExpanded() const; + void SetHighlighted(bool e) + { + highlighted_ = e; + + update(); + } + public slots: void SignalAllKeyframes(); + void SetExpanded(bool e); + + void ToggleExpanded(); + signals: void KeyframeAdded(NodeKeyframePtr key, int y); @@ -154,14 +165,11 @@ signals: void PinToggled(bool e); -public slots: - void SetExpanded(bool e); - - void ToggleExpanded(); - protected: virtual void changeEvent(QEvent *e) override; + virtual void paintEvent(QPaintEvent *event) override; + private: NodeParamViewItemTitleBar* title_bar_; @@ -171,6 +179,8 @@ private: rational time_; + bool highlighted_; + private slots: void Retranslate(); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 983dcdba4..64f871781 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -82,12 +82,13 @@ MainWindow::MainWindow(QWidget *parent) : AppendTimelinePanel(); audio_monitor_panel_ = PanelManager::instance()->CreatePanel(this); - // Make connections to sequence viewer + // Make node-related connections connect(node_panel_, &NodePanel::NodesSelected, param_panel_, &ParamPanel::SelectNodes); connect(node_panel_, &NodePanel::NodesDeselected, param_panel_, &ParamPanel::DeselectNodes); connect(node_panel_, &NodePanel::NodesSelected, table_panel_, &NodeTablePanel::SelectNodes); connect(node_panel_, &NodePanel::NodesDeselected, table_panel_, &NodeTablePanel::DeselectNodes); connect(param_panel_, &ParamPanel::RequestSelectNode, node_panel_, &NodePanel::Select); + connect(param_panel_, &ParamPanel::FocusedNodeChanged, sequence_viewer_panel_, &ViewerPanel::SetGizmos); // Connect time signals together connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTimestamp); From e09afd7f6831e39fc66f29a4f1bddd9840c7e936 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 18:27:30 +0100 Subject: [PATCH 26/74] Fix Issue#1241 When a Block node was deleted its linked blocks were not unlinked which caused a crash when the user tried to select a linked block. This fixes the issue by ensuring all links are removed when a Block node is delete. Undo/redo is also supported. --- app/widget/nodeview/nodeviewundo.cpp | 18 ++++++++++++++++++ app/widget/nodeview/nodeviewundo.h | 1 + 2 files changed, 19 insertions(+) diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index b3eef2300..4c05dd303 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -154,6 +154,16 @@ void NodeRemoveCommand::redo_internal() // Take nodes from graph (TakeNode() will automatically disconnect edges) foreach (Node* n, nodes_) { + // If the node is a block, unlink any linked blocks before removing + if (n->IsBlock()) { + Block *b = static_cast(n); + if (b->HasLinks()) { + linked_blocks_.insert(b, b->linked_clips().toList()); + foreach(Block * link, b->linked_clips()) { + b->Unlink(b, link); + } + } + } graph_->TakeNode(n, &memory_manager_); } } @@ -163,6 +173,13 @@ void NodeRemoveCommand::undo_internal() // Re-add nodes to graph foreach (Node* n, nodes_) { graph_->AddNode(n); + // If the node is a block re-link any previous links + if (n->IsBlock()) { + Block *b = static_cast(n); + foreach(Block * link, linked_blocks_[b]) { + b->Link(b, link); + } + } } // Re-connect edges @@ -171,6 +188,7 @@ void NodeRemoveCommand::undo_internal() } edges_.clear(); + linked_blocks_.clear(); } Project *NodeRemoveCommand::GetRelevantProject() const diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 4a9faefd4..181b0e34b 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -112,6 +112,7 @@ private: NodeGraph* graph_; QList nodes_; QList edges_; + QMap> linked_blocks_; }; class NodeRemoveWithExclusiveDeps : public UndoCommand { From 9d27d557cdbad0eb3ac77e8a99bd2e4fcc66e361 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 30 Sep 2020 18:14:11 +0100 Subject: [PATCH 27/74] Replace custom unlink with BlockUnlinkAllCommand Re-use existing unlink command and move all the unlinking code to NodeRemoveCommand to cover all bases (deletion of a block in the timeline or in the node editor). --- app/widget/nodeview/nodeviewundo.cpp | 22 +++++++++----------- app/widget/nodeview/nodeviewundo.h | 3 ++- app/widget/timelinewidget/timelinewidget.cpp | 2 -- app/widget/timelinewidget/undo/undo.cpp | 4 ---- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index 4c05dd303..032cd0bcd 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -158,10 +158,9 @@ void NodeRemoveCommand::redo_internal() if (n->IsBlock()) { Block *b = static_cast(n); if (b->HasLinks()) { - linked_blocks_.insert(b, b->linked_clips().toList()); - foreach(Block * link, b->linked_clips()) { - b->Unlink(b, link); - } + BlockUnlinkAllCommand *unlink_command = new BlockUnlinkAllCommand(b); + unlink_command->redo(); + block_unlink_commands_.append(unlink_command); } } graph_->TakeNode(n, &memory_manager_); @@ -173,13 +172,12 @@ void NodeRemoveCommand::undo_internal() // Re-add nodes to graph foreach (Node* n, nodes_) { graph_->AddNode(n); - // If the node is a block re-link any previous links - if (n->IsBlock()) { - Block *b = static_cast(n); - foreach(Block * link, linked_blocks_[b]) { - b->Link(b, link); - } - } + } + + // Relink any blocks that were unlinked + foreach(BlockUnlinkAllCommand* command, block_unlink_commands_) { + command->undo(); + delete command; } // Re-connect edges @@ -188,7 +186,7 @@ void NodeRemoveCommand::undo_internal() } edges_.clear(); - linked_blocks_.clear(); + block_unlink_commands_.clear(); } Project *NodeRemoveCommand::GetRelevantProject() const diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 181b0e34b..85230c803 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -27,6 +27,7 @@ #include "node/node.h" #include "nodeviewitem.h" #include "undo/undocommand.h" +#include "widget/timelinewidget/undo/undo.h" OLIVE_NAMESPACE_ENTER @@ -112,7 +113,7 @@ private: NodeGraph* graph_; QList nodes_; QList edges_; - QMap> linked_blocks_; + QList block_unlink_commands_; }; class NodeRemoveWithExclusiveDeps : public UndoCommand { diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index f8b9cb73d..076af1cd1 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -494,8 +494,6 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QList &blocks, new TrackReplaceBlockWithGapCommand(original_track, b, command); if (remove_from_graph) { - new BlockUnlinkAllCommand(b, command); - new NodeRemoveWithExclusiveDeps(static_cast(b->parent()), b, command); } } diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp index 36ab7a6bd..9732ffa08 100644 --- a/app/widget/timelinewidget/undo/undo.cpp +++ b/app/widget/timelinewidget/undo/undo.cpp @@ -243,10 +243,6 @@ void TrackRippleRemoveAreaCommand::redo_internal() foreach (Block* remove_block, removed_blocks_) { track_->RippleRemoveBlock(remove_block); - BlockUnlinkAllCommand* unlink_command = new BlockUnlinkAllCommand(remove_block); - unlink_command->redo(); - remove_block_commands_.append(unlink_command); - NodeRemoveWithExclusiveDeps* remove_command = new NodeRemoveWithExclusiveDeps(static_cast(remove_block->parent()), remove_block); remove_command->redo(); remove_block_commands_.append(remove_command); From 2fb706615571c708822cded078388a412e0345d5 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 2 Oct 2020 13:25:49 +1000 Subject: [PATCH 28/74] nodes: allow stalling graph signals until an operation is over --- app/node/graph.cpp | 89 +++++++++++++++++++- app/node/graph.h | 20 ++++- app/node/node.h | 2 + app/node/output/viewer/viewer.cpp | 35 +++++++- app/node/output/viewer/viewer.h | 8 +- app/timeline/trackreference.cpp | 5 ++ app/timeline/trackreference.h | 3 + app/widget/nodeview/nodeviewundo.h | 58 +++++++++++++ app/widget/timelinewidget/timelinewidget.cpp | 72 ++++++++++------ app/widget/timelinewidget/timelinewidget.h | 2 +- app/widget/timelinewidget/tool/pointer.cpp | 4 + app/widget/timelinewidget/undo/undo.cpp | 16 ++-- app/widget/timelinewidget/undo/undo.h | 2 + app/window/mainwindow/mainwindow.cpp | 4 +- 14 files changed, 277 insertions(+), 43 deletions(-) diff --git a/app/node/graph.cpp b/app/node/graph.cpp index b17132fb3..aa7515f9a 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -22,6 +22,11 @@ OLIVE_NAMESPACE_ENTER +NodeGraph::NodeGraph() : + operation_stack_(0) +{ +} + void NodeGraph::Clear() { foreach (Node* node, node_children_) { @@ -38,14 +43,94 @@ void NodeGraph::AddNode(Node *node) node->setParent(this); - connect(node, &Node::EdgeAdded, this, &NodeGraph::EdgeAdded); - connect(node, &Node::EdgeRemoved, this, &NodeGraph::EdgeRemoved); + connect(node, &Node::EdgeAdded, this, &NodeGraph::SignalEdgeAdded); + connect(node, &Node::EdgeRemoved, this, &NodeGraph::SignalEdgeRemoved); node_children_.append(node); emit NodeAdded(node); } +void NodeGraph::BeginOperation() +{ + operation_stack_++; +} + +void NodeGraph::EndOperation() +{ + operation_stack_--; + + if (!operation_stack_) { + // Signal everything that we cached during the operation + + // First, signal the removed edges + foreach (NodeEdgePtr e, cached_removed_edges_) { + emit EdgeRemoved(e); + } + cached_removed_edges_.clear(); + + // Next, signal the removed nodes + foreach (Node* n, cached_removed_nodes_) { + emit NodeRemoved(n); + } + cached_removed_nodes_.clear(); + + // Next, signal the added nodes + foreach (Node* n, cached_added_nodes_) { + emit NodeAdded(n); + } + cached_added_nodes_.clear(); + + // Finally, signal the added edges + foreach (NodeEdgePtr e, cached_added_edges_) { + emit EdgeAdded(e); + } + cached_added_edges_.clear(); + } +} + +void NodeGraph::SignalNodeAdded(Node* node) +{ + if (!operation_stack_) { + emit NodeAdded(node); + } else if (!cached_removed_nodes_.removeOne(node)) { + // If we already removed this node during the operation (appending a signal to + // cached_removed_nodes_), we just remove that instead of appending a new signal. However if we + // didn't (removeOne returning false), only then do we append an add signal + cached_added_nodes_.append(node); + } +} + +void NodeGraph::SignalNodeRemoved(Node *node) +{ + if (!operation_stack_) { + emit NodeRemoved(node); + } else if (!cached_added_nodes_.removeOne(node)) { + // See SignalNodeAdded() for explanation of this + cached_removed_nodes_.append(node); + } +} + +void NodeGraph::SignalEdgeAdded(NodeEdgePtr edge) +{ + if (!operation_stack_) { + emit EdgeAdded(edge); + } else if (!cached_removed_edges_.removeOne(edge)) { + // See SignalNodeAdded() for explanation of this + cached_added_edges_.append(edge); + } +} + +void NodeGraph::SignalEdgeRemoved(NodeEdgePtr edge) +{ + if (!operation_stack_) { + emit EdgeRemoved(edge); + } else if (!cached_added_edges_.removeOne(edge)) { + // See SignalNodeAdded() for explanation of this + cached_removed_edges_.append(edge); + } +} + void NodeGraph::TakeNode(Node *node, QObject* new_parent) { if (!ContainsNode(node)) { diff --git a/app/node/graph.h b/app/node/graph.h index 8ba686886..68324eb7a 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -37,7 +37,7 @@ public: /** * @brief NodeGraph Constructor */ - NodeGraph() = default; + NodeGraph(); /** * @brief Destructively destroys all nodes in the graph @@ -67,6 +67,10 @@ public: */ bool ContainsNode(Node* n) const; + void BeginOperation(); + + void EndOperation(); + signals: /** * @brief Signal emitted when a Node is added to the graph @@ -90,6 +94,20 @@ signals: private: QList node_children_; + + int operation_stack_; + + QList cached_added_nodes_; + QList cached_removed_nodes_; + QList cached_added_edges_; + QList cached_removed_edges_; + +private slots: + void SignalNodeAdded(Node *node); + void SignalNodeRemoved(Node* node); + void SignalEdgeAdded(NodeEdgePtr edge); + void SignalEdgeRemoved(NodeEdgePtr edge); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/node/node.h b/app/node/node.h index ba05359ce..04b96113c 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -528,6 +528,8 @@ T* Node::FindOutputNode() return static_cast(FindOutputNodeInternal(this)); } +using NodePtr = std::shared_ptr; + OLIVE_NAMESPACE_EXIT #endif // NODE_H diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 169de22cc..b88fb86ba 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -52,7 +52,7 @@ ViewerOutput::ViewerOutput() : connect(list, &TrackList::TrackListChanged, this, &ViewerOutput::UpdateTrackCache); connect(list, &TrackList::LengthChanged, this, &ViewerOutput::VerifyLength); connect(list, &TrackList::BlockAdded, this, &ViewerOutput::TrackListAddedBlock); - connect(list, &TrackList::BlockRemoved, this, &ViewerOutput::BlockRemoved); + connect(list, &TrackList::BlockRemoved, this, &ViewerOutput::SignalBlockRemoved); connect(list, &TrackList::TrackAdded, this, &ViewerOutput::TrackListAddedTrack); connect(list, &TrackList::TrackRemoved, this, &ViewerOutput::TrackRemoved); connect(list, &TrackList::TrackHeightChanged, this, &ViewerOutput::TrackHeightChangedSlot); @@ -274,6 +274,27 @@ void ViewerOutput::set_media_name(const QString &name) emit MediaNameChanged(media_name_); } +void ViewerOutput::SignalBlockAdded(Block *block, const TrackReference& track) +{ + if (!operation_stack_) { + emit BlockAdded(block, track); + } else { + cached_block_removed_.removeOne(block); + cached_block_added_.insert(block, track); + } +} + +void ViewerOutput::SignalBlockRemoved(Block *block) +{ + if (!operation_stack_) { + emit BlockRemoved({block}); + } else { + // We keep track of all blocks that are removed, even if we don't end up signalling them + cached_block_added_.remove(block); + cached_block_removed_.append(block); + } +} + void ViewerOutput::BeginOperation() { operation_stack_++; @@ -285,13 +306,23 @@ void ViewerOutput::EndOperation() { operation_stack_--; + if (!operation_stack_) { + for (auto it=cached_block_added_.cbegin(); it!=cached_block_added_.cend(); it++) { + emit BlockAdded(it.key(), it.value()); + } + cached_block_added_.clear(); + + emit BlockRemoved(cached_block_removed_); + cached_block_removed_.clear(); + } + Node::EndOperation(); } void ViewerOutput::TrackListAddedBlock(Block *block, int index) { Timeline::TrackType type = static_cast(sender())->type(); - emit BlockAdded(block, TrackReference(type, index)); + SignalBlockAdded(block, TrackReference(type, index)); } void ViewerOutput::TrackListAddedTrack(TrackOutput *track) diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 96c30cdf9..b4aef96a0 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -139,7 +139,7 @@ signals: void AudioParamsChanged(); void BlockAdded(Block* block, TrackReference track); - void BlockRemoved(Block* block); + void BlockRemoved(const QList& blocks); void TrackAdded(TrackOutput* track, Timeline::TrackType type); void TrackRemoved(TrackOutput* track); @@ -149,6 +149,9 @@ signals: void MediaNameChanged(const QString& name); private: + QMap cached_block_added_; + QList cached_block_removed_; + QUuid uuid_; NodeInput* texture_input_; @@ -186,6 +189,9 @@ private slots: void TrackHeightChangedSlot(int index, int height); + void SignalBlockAdded(Block *block, const TrackReference &track); + void SignalBlockRemoved(Block *block); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/timeline/trackreference.cpp b/app/timeline/trackreference.cpp index 6c046df7a..7080ea186 100644 --- a/app/timeline/trackreference.cpp +++ b/app/timeline/trackreference.cpp @@ -49,6 +49,11 @@ bool TrackReference::operator==(const TrackReference &ref) const return type_ == ref.type_ && index_ == ref.index_; } +bool TrackReference::operator!=(const TrackReference &ref) const +{ + return !(*this == ref); +} + uint qHash(const TrackReference &r, uint seed) { // Not super efficient, but couldn't think of any better way to ensure a different hash each time diff --git a/app/timeline/trackreference.h b/app/timeline/trackreference.h index 842985168..7ae13d444 100644 --- a/app/timeline/trackreference.h +++ b/app/timeline/trackreference.h @@ -40,10 +40,13 @@ public: bool operator==(const TrackReference& ref) const; + bool operator!=(const TrackReference& ref) const; + private: Timeline::TrackType type_; int index_; + }; uint qHash(const TrackReference& r, uint seed); diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 4a9faefd4..05837a21d 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -149,6 +149,64 @@ private: }; +class NodeGraphBeginOperationCommand : public UndoCommand { +public: + NodeGraphBeginOperationCommand(NodeGraph* graph, QUndoCommand* parent = nullptr) : + UndoCommand(parent), + graph_(graph) + { + } + + virtual Project* GetRelevantProject() const override + { + return static_cast(graph_)->project(); + } + +protected: + virtual void redo_internal() override + { + graph_->BeginOperation(); + } + + virtual void undo_internal() override + { + graph_->EndOperation(); + } + +private: + NodeGraph* graph_; + +}; + +class NodeGraphEndOperationCommand : public UndoCommand { +public: + NodeGraphEndOperationCommand(NodeGraph* graph, QUndoCommand* parent = nullptr) : + UndoCommand(parent), + graph_(graph) + { + } + + virtual Project* GetRelevantProject() const override + { + return static_cast(graph_)->project(); + } + +protected: + virtual void redo_internal() override + { + graph_->EndOperation(); + } + + virtual void undo_internal() override + { + graph_->BeginOperation(); + } + +private: + NodeGraph* graph_; + +}; + OLIVE_NAMESPACE_EXIT #endif // NODEVIEWUNDO_H diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index f8b9cb73d..bfb186655 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -918,40 +918,62 @@ void TimelineWidget::ViewDragDropped(TimelineViewMouseEvent *event) void TimelineWidget::AddBlock(Block *block, TrackReference track) { // Set up clip with view parameters (clip item will automatically size its rect accordingly) - TimelineViewBlockItem* item = new TimelineViewBlockItem(block); + TimelineViewBlockItem* item = block_items_.value(block); - item->SetYCoords(GetTrackY(track), GetTrackHeight(track)); - item->SetScale(GetScale()); - item->SetTrack(track); - item->SetTimebase(timebase()); + if (!item) { - // Add to list of clip items that can be iterated through - block_items_.insert(block, item); + // Add to list of clip items that can be iterated through + item = new TimelineViewBlockItem(block); + block_items_.insert(block, item); - // Add item to graphics scene - views_.at(track.type())->view()->scene()->addItem(item); + // Set scale parameters + item->SetScale(GetScale()); + item->SetTimebase(timebase()); + item->SetYCoords(GetTrackY(track), GetTrackHeight(track)); + item->SetTrack(track); - connect(block, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed); - connect(block, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated); - connect(block, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated); - connect(block, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated); + // Add item to graphics scene + views_.at(track.type())->view()->scene()->addItem(item); + + connect(block, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed); + connect(block, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated); + connect(block, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated); + connect(block, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated); + + } else if (item->Track() != track) { + + item->SetYCoords(GetTrackY(track), GetTrackHeight(track)); + item->SetTrack(track); + + } } -void TimelineWidget::RemoveBlock(Block *block) +void TimelineWidget::RemoveBlock(const QList &blocks) { - disconnect(block, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed); - disconnect(block, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated); - disconnect(block, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated); - disconnect(block, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated); + QList delete_items; + delete_items.reserve(blocks.size()); - TimelineViewBlockItem* item = block_items_.take(block); + QList deselect_blocks; - if (item->isSelected()) { - // Sending a list of one item all the time is not very efficient - emit BlocksDeselected({block}); + foreach (Block* b, blocks) { + disconnect(b, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed); + disconnect(b, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated); + disconnect(b, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated); + disconnect(b, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated); + + TimelineViewBlockItem* item = block_items_.take(b); + delete_items.append(item); + + if (item->isSelected()) { + deselect_blocks.append(b); + } } - delete item; + if (!deselect_blocks.isEmpty()) { + emit BlocksDeselected(deselect_blocks); + } + + qDeleteAll(delete_items); } void TimelineWidget::AddTrack(TrackOutput *track, Timeline::TrackType type) @@ -969,9 +991,7 @@ void TimelineWidget::RemoveTrack(TrackOutput *track) disconnect(track, &TrackOutput::IndexChanged, this, &TimelineWidget::TrackIndexChanged); disconnect(track, &TrackOutput::PreviewChanged, this, &TimelineWidget::TrackPreviewUpdated); - foreach (Block* b, track->Blocks()) { - RemoveBlock(b); - } + RemoveBlock(track->Blocks()); } void TimelineWidget::TrackIndexChanged() diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index cb9bf5d64..d03b1e5a3 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -532,7 +532,7 @@ private slots: void ViewDragDropped(TimelineViewMouseEvent* event); void AddBlock(Block* block, TrackReference track); - void RemoveBlock(Block* block); + void RemoveBlock(const QList& blocks); void AddTrack(TrackOutput* track, Timeline::TrackType type); void RemoveTrack(TrackOutput* track); diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 9889a30a3..200c0aa6d 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -555,6 +555,8 @@ void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event) QUndoCommand* command = new QUndoCommand(); + new NodeGraphBeginOperationCommand(static_cast(parent()->GetConnectedNode()->parent()), command); + foreach (const GhostBlockPair& p, blocks_trimming) { TimelineViewGhostItem* ghost = p.ghost; @@ -676,6 +678,8 @@ void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event) } } + new NodeGraphEndOperationCommand(static_cast(parent()->GetConnectedNode()->parent()), command); + Core::instance()->undo_stack()->pushIfHasChildren(command); } diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp index 36ab7a6bd..c807c46c2 100644 --- a/app/widget/timelinewidget/undo/undo.cpp +++ b/app/widget/timelinewidget/undo/undo.cpp @@ -295,7 +295,7 @@ void TrackRippleRemoveAreaCommand::undo_internal() track_->RippleRemoveBlock(trim_in_); trim_out_->set_length_and_media_out(trim_out_old_length_); - delete TakeNodeFromParentGraph(trim_in_); + TakeNodeFromParentGraph(trim_in_, &memory_manager_); } else { @@ -390,7 +390,7 @@ void TrackPlaceBlockCommand::undo_internal() if (gap_ != nullptr) { track_->RippleRemoveBlock(gap_); - delete TakeNodeFromParentGraph(gap_); + TakeNodeFromParentGraph(gap_, &memory_manager_); } } else { TrackRippleRemoveAreaCommand::undo_internal(); @@ -926,7 +926,7 @@ void BlockTrimCommand::undo_internal() if (we_created_adjacent_) { // If we created a gap, just remove it straight up track_->RippleRemoveBlock(adjacent_); - delete TakeNodeFromParentGraph(adjacent_); + TakeNodeFromParentGraph(adjacent_, &memory_manager_); adjacent_ = nullptr; we_created_adjacent_ = false; } else if (adjacent_) { @@ -1066,7 +1066,7 @@ void TrackReplaceBlockWithGapCommand::undo_internal() // We made this gap, simply swap our gap back track_->ReplaceBlock(our_gap_, block_); - delete TakeNodeFromParentGraph(our_gap_); + TakeNodeFromParentGraph(our_gap_, &memory_manager_); our_gap_ = nullptr; } else if (existing_gap_) { @@ -1157,7 +1157,7 @@ void TrackSlideCommand::slide_internal(bool undo) if (we_created_in_adjacent_) { // This is a gap we made, we can just delete it entirely track_->RippleRemoveBlock(in_adjacent_); - delete TakeNodeFromParentGraph(in_adjacent_); + TakeNodeFromParentGraph(in_adjacent_, &memory_manager_); we_created_in_adjacent_ = false; in_adjacent_ = nullptr; } else if (in_adjacent_->parent() == &memory_manager_) { @@ -1172,7 +1172,7 @@ void TrackSlideCommand::slide_internal(bool undo) if (we_created_out_adjacent_) { // This is a gap we made, we can just delete it entirely track_->RippleRemoveBlock(out_adjacent_); - delete TakeNodeFromParentGraph(out_adjacent_); + TakeNodeFromParentGraph(out_adjacent_, &memory_manager_); we_created_out_adjacent_ = false; out_adjacent_ = nullptr; } else if (out_adjacent_) { @@ -1490,7 +1490,7 @@ void TrackListRippleToolCommand::undo_internal() GapBlock* gap = working_data_.at(i).created_gap; info.track->RippleRemoveBlock(gap); - delete TakeNodeFromParentGraph(gap); + TakeNodeFromParentGraph(gap, &memory_manager_); } } } @@ -1593,7 +1593,7 @@ void TrackListInsertGaps::undo_internal() // Remove added gaps foreach (GapBlock* gap, gaps_added_) { TrackOutput::TrackFromBlock(gap)->RippleRemoveBlock(gap); - delete TakeNodeFromParentGraph(gap); + TakeNodeFromParentGraph(gap, &memory_manager_); } gaps_added_.clear(); diff --git a/app/widget/timelinewidget/undo/undo.h b/app/widget/timelinewidget/undo/undo.h index 5a37674bc..c8f53c00a 100644 --- a/app/widget/timelinewidget/undo/undo.h +++ b/app/widget/timelinewidget/undo/undo.h @@ -589,6 +589,8 @@ private: BlockSplitPreservingLinksCommand* split_command_; + QObject memory_manager_; + }; class TransitionRemoveCommand : public UndoCommand { diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 64f871781..07e81dea5 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -492,8 +492,8 @@ TimelinePanel* MainWindow::AppendTimelinePanel() 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::BlocksSelected, node_panel_, &NodePanel::SelectBlocks); - connect(panel, &TimelinePanel::BlocksDeselected, node_panel_, &NodePanel::DeselectBlocks); + //connect(panel, &TimelinePanel::BlocksSelected, node_panel_, &NodePanel::SelectBlocks); + //connect(panel, &TimelinePanel::BlocksDeselected, node_panel_, &NodePanel::DeselectBlocks); connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp); connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp); connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp); From 42049107966612776ca89e690b0823c9fabddb33 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 2 Oct 2020 16:04:50 +1000 Subject: [PATCH 29/74] nodes: link edges to IDs rather than objects Allows code to know whether the connected parameters has been removed or not. --- app/node/edge.cpp | 23 ++++++++++----- app/node/edge.h | 31 ++++++++++++++++---- app/node/inputarray.cpp | 19 ++++++++++++ app/node/inputarray.h | 4 +++ app/node/output/track/track.cpp | 7 ++++- app/node/output/track/track.h | 2 ++ app/node/output/track/tracklist.cpp | 8 +++-- app/node/output/track/tracklist.h | 2 +- app/node/output/viewer/viewer.cpp | 5 ++++ app/node/output/viewer/viewer.h | 2 ++ app/node/param.cpp | 6 ++-- app/node/param.h | 2 +- app/widget/nodeview/nodeview.cpp | 6 ++-- app/widget/timelinewidget/timelinewidget.cpp | 4 +++ app/widget/timelinewidget/undo/undo.cpp | 2 +- app/window/mainwindow/mainwindow.cpp | 27 +++++++++-------- 16 files changed, 112 insertions(+), 38 deletions(-) diff --git a/app/node/edge.cpp b/app/node/edge.cpp index 1765d6c1b..5f009b8f3 100644 --- a/app/node/edge.cpp +++ b/app/node/edge.cpp @@ -20,22 +20,31 @@ #include "edge.h" +#include "input.h" +#include "node.h" +#include "output.h" + OLIVE_NAMESPACE_ENTER -NodeEdge::NodeEdge(NodeOutput *output, NodeInput *input) : - output_(output), - input_(input) +NodeEdge::NodeEdge(NodeOutput *output, NodeInput *input) { + output_ = ParamToConnection(output); + input_ = ParamToConnection(input); } -NodeOutput *NodeEdge::output() +NodeOutput *NodeEdge::output() const { - return output_; + return output_.node->GetOutputWithID(output_.id); } -NodeInput *NodeEdge::input() +NodeInput *NodeEdge::input() const { - return input_; + return input_.node->GetInputWithID(input_.id); +} + +NodeEdge::Connection NodeEdge::ParamToConnection(NodeParam *param) +{ + return {param->parentNode(), param->id()}; } OLIVE_NAMESPACE_EXIT diff --git a/app/node/edge.h b/app/node/edge.h index 990fdc626..a79b8aec7 100644 --- a/app/node/edge.h +++ b/app/node/edge.h @@ -22,13 +22,16 @@ #define EDGE_H #include +#include #include "common/define.h" OLIVE_NAMESPACE_ENTER -class NodeOutput; +class Node; class NodeInput; +class NodeOutput; +class NodeParam; /** * @brief A connection between two node parameters (a NodeOutput and a NodeInput) @@ -44,19 +47,37 @@ public: */ NodeEdge(NodeOutput* output, NodeInput* input); + Node* output_node() const + { + return output_.node; + } + + Node* input_node() const + { + return input_.node; + } + /** * @brief Return the output parameter this edge is connected to */ - NodeOutput* output(); + NodeOutput* output() const; /** * @brief Return the input parameter this edge is connected to */ - NodeInput* input(); + NodeInput* input() const; private: - NodeOutput* output_; - NodeInput* input_; + struct Connection { + Node* node; + QString id; + }; + + static Connection ParamToConnection(NodeParam* param); + + Connection output_; + Connection input_; + }; using NodeEdgePtr = std::shared_ptr; diff --git a/app/node/inputarray.cpp b/app/node/inputarray.cpp index b1ec0caa8..d9be9bf58 100644 --- a/app/node/inputarray.cpp +++ b/app/node/inputarray.cpp @@ -33,6 +33,12 @@ NodeInputArray::NodeInputArray(const QString &id, const DataType &type, const QV { } +NodeInputArray::~NodeInputArray() +{ + // Clear all connected edges (make sure our override is called) + DisconnectAll(); +} + bool NodeInputArray::IsArray() const { return true; @@ -58,6 +64,10 @@ void NodeInputArray::SetSize(int size) if (size < old_size) { // If the new size is less, delete all extraneous parameters + for (int i=size;iDisconnectAll(); + } + for (int i=size;i &NodeInputArray::sub_params() return sub_params_; } +void NodeInputArray::DisconnectAll() +{ + NodeParam::DisconnectAll(); + + foreach (NodeInput* input, sub_params_) { + input->DisconnectAll(); + } +} + void NodeInputArray::InsertAt(int index) { // Add another input at the end diff --git a/app/node/inputarray.h b/app/node/inputarray.h index 8a935f73e..5aac757ac 100644 --- a/app/node/inputarray.h +++ b/app/node/inputarray.h @@ -31,6 +31,8 @@ class NodeInputArray : public NodeInput public: NodeInputArray(const QString &id, const DataType& type, const QVariant& default_value = 0); + virtual ~NodeInputArray() override; + virtual bool IsArray() const override; int GetSize() const; @@ -51,6 +53,8 @@ public: const QVector& sub_params(); + virtual void DisconnectAll() override; + signals: void SizeChanged(int size); diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 92eb93290..3c0c11ced 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -55,6 +55,11 @@ TrackOutput::TrackOutput() : track_height_ = kTrackHeightDefault; } +TrackOutput::~TrackOutput() +{ + DisconnectAll(); +} + void TrackOutput::set_track_type(const Timeline::TrackType &track_type) { track_type_ = track_type; @@ -553,7 +558,7 @@ void TrackOutput::BlockConnected(NodeEdgePtr edge) void TrackOutput::BlockDisconnected(NodeEdgePtr edge) { - Block* b = static_cast(edge->output()->parentNode()); + Block* b = static_cast(edge->output_node()); if (block_cache_.contains(b)) { block_cache_.removeOne(b); diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 954b9b45f..dc02b1979 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -36,6 +36,8 @@ class TrackOutput : public Node public: TrackOutput(); + virtual ~TrackOutput() override; + const Timeline::TrackType& track_type() const; void set_track_type(const Timeline::TrackType& track_type); diff --git a/app/node/output/track/tracklist.cpp b/app/node/output/track/tracklist.cpp index 9871baabb..835e031c5 100644 --- a/app/node/output/track/tracklist.cpp +++ b/app/node/output/track/tracklist.cpp @@ -134,7 +134,7 @@ TrackOutput* TrackList::AddTrack() return track; } -void TrackList::RemoveTrack() +void TrackList::RemoveTrack(QObject* new_parent) { if (track_cache_.isEmpty()) { return; @@ -144,7 +144,11 @@ void TrackList::RemoveTrack() GetParentGraph()->TakeNode(track); - delete track; + if (!new_parent) { + delete track; + } else { + track->setParent(new_parent); + } track_input_->RemoveLast(); } diff --git a/app/node/output/track/tracklist.h b/app/node/output/track/tracklist.h index d0923f577..78a7aa035 100644 --- a/app/node/output/track/tracklist.h +++ b/app/node/output/track/tracklist.h @@ -44,7 +44,7 @@ public: TrackOutput *AddTrack(); - void RemoveTrack(); + void RemoveTrack(QObject *new_parent); const rational& GetTotalLength() const; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index b88fb86ba..ca99b0837 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -62,6 +62,11 @@ ViewerOutput::ViewerOutput() : uuid_ = QUuid::createUuid(); } +ViewerOutput::~ViewerOutput() +{ + DisconnectAll(); +} + Node *ViewerOutput::copy() const { return new ViewerOutput(); diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index b4aef96a0..94185f2ac 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -47,6 +47,8 @@ class ViewerOutput : public Node public: ViewerOutput(); + virtual ~ViewerOutput() override; + virtual Node* copy() const override; virtual QString Name() const override; diff --git a/app/node/param.cpp b/app/node/param.cpp index 601b393da..de6453a7d 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -43,9 +43,7 @@ NodeParam::NodeParam(const QString &id) : NodeParam::~NodeParam() { // Clear all connected edges - while (!edges_.isEmpty()) { - DisconnectEdge(edges_.last()); - } + DisconnectAll(); } const QString NodeParam::id() const @@ -111,7 +109,7 @@ const QVector &NodeParam::edges() void NodeParam::DisconnectAll() { while (!edges_.isEmpty()) { - DisconnectEdge(edges_.first()); + DisconnectEdge(edges_.last()); } } diff --git a/app/node/param.h b/app/node/param.h index 0157d7bdd..553dbe248 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -319,7 +319,7 @@ public: /** * @brief Disconnect any edges connecting this parameter to other parameters */ - void DisconnectAll(); + virtual void DisconnectAll(); /** * @brief Connect an output parameter to an input parameter diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index b8d7e526f..77d3e250f 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -922,8 +922,8 @@ void NodeView::UpdateBlockFilter() // Show only edges between those dependencies foreach (NodeViewEdge* edge, scene_.edge_map()) { - edge->setVisible((currently_visible.contains(edge->edge()->input()->parentNode()) - && currently_visible.contains(edge->edge()->output()->parentNode()))); + edge->setVisible((currently_visible.contains(edge->edge()->input_node()) + && currently_visible.contains(edge->edge()->output_node()))); } } @@ -1024,7 +1024,7 @@ void NodeView::GraphEdgeRemoved(NodeEdgePtr edge) { scene_.RemoveEdge(edge); - Node* output_node = edge->output()->parentNode(); + Node* output_node = edge->output_node(); // Check if this disconnected node still connects to a selected block, in which case do nothing foreach (Block* b, selected_blocks_) { diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index bfb186655..6bb7c11b2 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -271,6 +271,10 @@ void TimelineWidget::DisconnectNodeInternal(ViewerOutput *n) disconnect(n, &ViewerOutput::TimebaseChanged, this, &TimelineWidget::SetTimebase); disconnect(n, &ViewerOutput::TrackHeightChanged, this, &TimelineWidget::TrackHeightChanged); + foreach (TrackOutput* track, n->GetTracks()) { + RemoveTrack(track); + } + ruler()->SetPlaybackCache(nullptr); SetTimebase(0); diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp index c807c46c2..dd6a48b09 100644 --- a/app/widget/timelinewidget/undo/undo.cpp +++ b/app/widget/timelinewidget/undo/undo.cpp @@ -397,7 +397,7 @@ void TrackPlaceBlockCommand::undo_internal() } for (;added_track_count_>0;added_track_count_--) { - timeline_->RemoveTrack(); + timeline_->RemoveTrack(&memory_manager_); } } diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 07e81dea5..cf5424700 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -319,19 +319,6 @@ void MainWindow::ProjectOpen(Project *p) void MainWindow::ProjectClose(Project *p) { - // Close project from project panel - foreach (ProjectPanel* panel, project_panels_) { - if (panel->project() == p) { - RemoveProjectPanel(panel); - } - } - - foreach (ProjectPanel* panel, folder_panels_) { - if (panel->project() == p) { - panel->close(); - } - } - // Close any open sequences from project QList open_sequences = p->get_items_of_type(Item::kSequence); @@ -358,6 +345,20 @@ void MainWindow::ProjectClose(Project *p) } } } + + // Close any extra folder panels + foreach (ProjectPanel* panel, folder_panels_) { + if (panel->project() == p) { + panel->close(); + } + } + + // Close project from project panel + foreach (ProjectPanel* panel, project_panels_) { + if (panel->project() == p) { + RemoveProjectPanel(panel); + } + } } void MainWindow::SetApplicationProgressStatus(ProgressStatus status) From 0508acc6133c5abb49ced38feebe9f7bd284cc4c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 2 Oct 2020 17:10:15 +1000 Subject: [PATCH 30/74] timeline: began crude edit tool functionality --- app/node/block/block.cpp | 5 ++ app/node/block/block.h | 2 + app/widget/timelinewidget/timelinewidget.cpp | 71 ++++++++++++++----- app/widget/timelinewidget/timelinewidget.h | 17 +++++ app/widget/timelinewidget/tool/edit.cpp | 38 +++++++++- app/widget/timelinewidget/tool/pointer.cpp | 7 +- .../timelinewidget/view/timelineview.cpp | 19 +++++ app/widget/timelinewidget/view/timelineview.h | 7 ++ .../view/timelineviewblockitem.cpp | 4 -- 9 files changed, 143 insertions(+), 27 deletions(-) diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 85df9aa3b..79264c572 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -122,6 +122,11 @@ void Block::set_length_and_media_in(const rational &length) LengthChangedEvent(old_length, length, Timeline::kTrimIn); } +TimeRange Block::range() const +{ + return TimeRange(in(), out()); +} + Block *Block::previous() { return previous_; diff --git a/app/node/block/block.h b/app/node/block/block.h index 7fa036302..ca5c700ee 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -54,6 +54,8 @@ public: void set_length_and_media_out(const rational &length); void set_length_and_media_in(const rational &length); + TimeRange range() const; + Block* previous(); Block* next(); void set_previous(Block* previous); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 6bb7c11b2..b21bf6092 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -107,6 +107,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) : view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); view->SetSnapService(this); + view->SetSelectionList(&selections_); view_splitter_->addWidget(tview); @@ -165,7 +166,7 @@ void TimelineWidget::Clear() QMap::const_iterator iterator; for (iterator=block_items_.begin(); iterator!=block_items_.end(); iterator++) { - if (iterator.value()->isSelected()) { + if (IsItemSelected(iterator.value())) { deselected_blocks.append(iterator.key()); } @@ -384,8 +385,8 @@ void TimelineWidget::SelectAll() QMap::const_iterator i; for (i=block_items_.constBegin(); i!=block_items_.end(); i++) { - if (!i.value()->isSelected()) { - i.value()->setSelected(true); + if (!IsItemSelected(i.value())) { + AddSelection(i.value()); blocks_selected.append(i.key()); } } @@ -400,8 +401,8 @@ void TimelineWidget::DeselectAll() QMap::const_iterator i; for (i=block_items_.constBegin(); i!=block_items_.end(); i++) { - if (i.value()->isSelected()) { - i.value()->setSelected(false); + if (IsItemSelected(i.value())) { + RemoveSelection(i.value()); blocks_deselected.append(i.key()); } } @@ -791,7 +792,7 @@ QList TimelineWidget::GetSelectedBlocks() TimelineViewBlockItem* item = iterator.value(); - if (item && item->isSelected()) { + if (item && IsItemSelected(item)) { list.append(item); } } @@ -893,9 +894,8 @@ void TimelineWidget::ViewMouseReleased(TimelineViewMouseEvent *event) void TimelineWidget::ViewMouseDoubleClicked(TimelineViewMouseEvent *event) { - if (GetConnectedNode() && active_tool_ != nullptr) { - active_tool_->MouseDoubleClick(event); - active_tool_ = nullptr; + if (GetConnectedNode()) { + GetActiveTool()->MouseDoubleClick(event); } } @@ -968,7 +968,7 @@ void TimelineWidget::RemoveBlock(const QList &blocks) TimelineViewBlockItem* item = block_items_.take(b); delete_items.append(item); - if (item->isSelected()) { + if (IsItemSelected(item)) { deselect_blocks.append(b); } } @@ -1211,8 +1211,14 @@ void TimelineWidget::SetBlockLinksSelected(Block* block, bool selected) TimelineViewBlockItem* link_item; foreach (Block* link, block->linked_clips()) { - if ((link_item = block_items_[link]) != nullptr) { - link_item->setSelected(selected); + link_item = block_items_.value(link); + + if (link_item) { + if (selected) { + AddSelection(link_item); + } else { + RemoveSelection(link_item); + } } } } @@ -1383,7 +1389,7 @@ void TimelineWidget::StartRubberBandSelect(bool enable_selecting, bool select_li // We don't touch any blocks that are already selected. If you want these to be deselected by // default, call DeselectAll() befoer calling StartRubberBandSelect() foreach (TimelineViewBlockItem* block, block_items_) { - if (block->isSelected()) { + if (IsItemSelected(block)) { rubberband_already_selected_.append(block); } } @@ -1428,7 +1434,11 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin } foreach (QGraphicsItem* item, rubberband_now_selected_) { - item->setSelected(false); + TimelineViewBlockItem* block_item = dynamic_cast(item); + + if (block_item) { + RemoveSelection(block_item); + } } // Cache limit because we append to this array in this loop and don't need to process those @@ -1446,7 +1456,7 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin // Since new_selected_list is filtered by rubberband_already_selected_, this should certainly // be deselected by now - block_item->setSelected(true); + AddSelection(block_item); if (select_links) { // Select the block's links @@ -1456,7 +1466,7 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin TimelineViewBlockItem* link_item; foreach (Block* link, b->linked_clips()) { if ((link_item = block_items_[link]) != nullptr) { - link_item->setSelected(true); + AddSelection(link_item); if (!new_selected_list.contains(link_item) && !rubberband_already_selected_.contains(link_item)) { @@ -1485,6 +1495,35 @@ void TimelineWidget::EndRubberBandSelect() rubberband_already_selected_.clear(); } +void TimelineWidget::AddSelection(const TimeRange &time, const TrackReference &track) +{ + selections_[track].InsertTimeRange(time); + + views_.at(track.type())->view()->viewport()->update(); +} + +void TimelineWidget::AddSelection(TimelineViewBlockItem *item) +{ + AddSelection(item->block()->range(), item->Track()); +} + +void TimelineWidget::RemoveSelection(const TimeRange &time, const TrackReference &track) +{ + selections_[track].RemoveTimeRange(time); + + views_.at(track.type())->view()->viewport()->update(); +} + +void TimelineWidget::RemoveSelection(TimelineViewBlockItem *item) +{ + RemoveSelection(item->block()->range(), item->Track()); +} + +bool TimelineWidget::IsItemSelected(TimelineViewBlockItem *item) const +{ + return selections_[item->Track()].ContainsTimeRange(item->block()->range()); +} + struct SnapData { rational time; rational movement; diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index d03b1e5a3..f7121af03 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -361,6 +361,13 @@ private: virtual void MousePress(TimelineViewMouseEvent *event) override; virtual void MouseMove(TimelineViewMouseEvent *event) override; virtual void MouseRelease(TimelineViewMouseEvent *event) override; + virtual void MouseDoubleClick(TimelineViewMouseEvent *event) override; + + private: + QHash start_selections_; + + TimelineCoordinate start_coord_; + }; class RazorTool : public BeamTool @@ -481,6 +488,16 @@ private: QList rubberband_already_selected_; QList rubberband_now_selected_; + QHash selections_; + + void AddSelection(const TimeRange& time, const TrackReference& track); + void AddSelection(TimelineViewBlockItem* item); + + void RemoveSelection(const TimeRange& time, const TrackReference& track); + void RemoveSelection(TimelineViewBlockItem* item); + + bool IsItemSelected(TimelineViewBlockItem* item) const; + Tool* GetActiveTool(); QVector tools_; diff --git a/app/widget/timelinewidget/tool/edit.cpp b/app/widget/timelinewidget/tool/edit.cpp index cb804bebc..e8b79f312 100644 --- a/app/widget/timelinewidget/tool/edit.cpp +++ b/app/widget/timelinewidget/tool/edit.cpp @@ -29,17 +29,49 @@ TimelineWidget::EditTool::EditTool(TimelineWidget* parent) : void TimelineWidget::EditTool::MousePress(TimelineViewMouseEvent *event) { - Q_UNUSED(event) + if (!(event->GetModifiers() & Qt::ShiftModifier)) { + parent()->DeselectAll(); + } } void TimelineWidget::EditTool::MouseMove(TimelineViewMouseEvent *event) { - Q_UNUSED(event) + if (dragging_) { + parent()->selections_ = start_selections_; + parent()->AddSelection(TimeRange(start_coord_.GetFrame(), event->GetFrame()), + start_coord_.GetTrack()); + } else { + start_selections_ = parent()->selections_; + + dragging_ = true; + + start_coord_ = event->GetCoordinates(true); + + // Snap if we're snapping + if (Core::instance()->snapping()) { + rational movement; + parent()->SnapPoint({start_coord_.GetFrame()}, &movement); + if (!movement.isNull()) { + start_coord_.SetFrame(start_coord_.GetFrame() + movement); + } + } + + dragging_ = true; + } } void TimelineWidget::EditTool::MouseRelease(TimelineViewMouseEvent *event) { - Q_UNUSED(event) + dragging_ = false; +} + +void TimelineWidget::EditTool::MouseDoubleClick(TimelineViewMouseEvent *event) +{ + TimelineViewBlockItem* item = GetItemAtScenePos(event->GetCoordinates()); + + if (item && !parent()->GetTrackFromReference(item->Track())->IsLocked()) { + parent()->AddSelection(item); + } } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 200c0aa6d..186b8e93e 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -52,7 +52,6 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event) clicked_item_ = GetItemAtScenePos(event->GetCoordinates()); bool selectable_item = (clicked_item_ - && clicked_item_->flags() & QGraphicsItem::ItemIsSelectable && !parent()->GetTrackFromReference(clicked_item_->Track())->IsLocked()); if (selectable_item) { @@ -76,14 +75,14 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event) } // If this item is already selected, no further selection needs to be made - if (clicked_item_->isSelected()) { + if (parent()->IsItemSelected(clicked_item_)) { // Collect item deselections QList deselected_blocks; // If shift is held, deselect it if (event->GetModifiers() & Qt::ShiftModifier) { - clicked_item_->setSelected(false); + parent()->RemoveSelection(clicked_item_); deselected_blocks.append(clicked_item_->block()); // If not holding alt, deselect all links as well @@ -110,7 +109,7 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event) QList selected_blocks; // Select this item - clicked_item_->setSelected(true); + parent()->AddSelection(clicked_item_); selected_blocks.append(clicked_item_->block()); // If not holding alt, select all links as well diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 4b60ec0b7..2f1d5c6e0 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -37,6 +37,7 @@ OLIVE_NAMESPACE_ENTER TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) : TimelineViewBase(parent), + selections_(nullptr), show_beam_cursor_(false), connected_track_list_(nullptr) { @@ -242,6 +243,24 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect) cursor_x, track_y + GetTrackHeight(track_index)); } + + if (selections_ && !selections_->isEmpty()) { + painter->setPen(Qt::NoPen); + painter->setBrush(QColor(0, 0, 0, 64)); + + for (auto it=selections_->cbegin(); it!=selections_->cend(); it++) { + if (it.key().type() == connected_track_list_->type()) { + int track_index = it.key().index(); + + foreach (const TimeRange& range, it.value()) { + painter->drawRect(TimeToScene(range.in()), + GetTrackY(track_index), + TimeToScene(range.length()), + GetTrackHeight(track_index)); + } + } + } + } } void TimelineView::ToolChangedEvent(Tool::Item tool) diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index e409b425d..67606dcea 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -59,6 +59,11 @@ public: void SetBeamCursor(const TimelineCoordinate& coord); + void SetSelectionList(QHash* s) + { + selections_ = s; + } + signals: void MousePressed(TimelineViewMouseEvent* event); void MouseMoved(TimelineViewMouseEvent* event); @@ -108,6 +113,8 @@ private: void UpdatePlayheadRect(); + QHash* selections_; + bool show_beam_cursor_; TimelineCoordinate cursor_coord_; diff --git a/app/widget/timelinewidget/view/timelineviewblockitem.cpp b/app/widget/timelinewidget/view/timelineviewblockitem.cpp index 790e1c59e..3d1225e11 100644 --- a/app/widget/timelinewidget/view/timelineviewblockitem.cpp +++ b/app/widget/timelinewidget/view/timelineviewblockitem.cpp @@ -43,10 +43,6 @@ TimelineViewBlockItem::TimelineViewBlockItem(Block *block, QGraphicsItem* parent { setBrush(Qt::white); setCursor(Qt::DragMoveCursor); - setFlag(QGraphicsItem::ItemIsSelectable, - block_->type() == Block::kClip - || block_->type() == Block::kGap - || block_->type() == Block::kTransition); UpdateRect(); } From dd3d11c7e0c29f69f4369d6eebfc2131ba99074d Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 2 Oct 2020 22:02:18 +1000 Subject: [PATCH 31/74] timeline: moved tools to separate files Code cleanup, TimelineWidget header was getting a little too bloated. --- app/config/config.cpp | 2 +- .../timelinewidget/timelinescaledobject.h | 2 +- app/widget/timelinewidget/timelinewidget.cpp | 88 +++- app/widget/timelinewidget/timelinewidget.h | 450 +++--------------- app/widget/timelinewidget/tool/CMakeLists.txt | 13 + app/widget/timelinewidget/tool/add.cpp | 19 +- app/widget/timelinewidget/tool/add.h | 47 ++ app/widget/timelinewidget/tool/beam.cpp | 9 +- app/widget/timelinewidget/tool/beam.h | 42 ++ app/widget/timelinewidget/tool/edit.cpp | 17 +- app/widget/timelinewidget/tool/edit.h | 48 ++ app/widget/timelinewidget/tool/import.cpp | 65 ++- app/widget/timelinewidget/tool/import.h | 91 ++++ app/widget/timelinewidget/tool/pointer.cpp | 95 ++-- app/widget/timelinewidget/tool/pointer.h | 127 +++++ app/widget/timelinewidget/tool/razor.cpp | 9 +- app/widget/timelinewidget/tool/razor.h | 43 ++ app/widget/timelinewidget/tool/ripple.cpp | 23 +- app/widget/timelinewidget/tool/ripple.h | 41 ++ app/widget/timelinewidget/tool/rolling.cpp | 5 +- app/widget/timelinewidget/tool/rolling.h | 40 ++ app/widget/timelinewidget/tool/slide.cpp | 5 +- app/widget/timelinewidget/tool/slide.h | 41 ++ app/widget/timelinewidget/tool/slip.cpp | 17 +- app/widget/timelinewidget/tool/slip.h | 40 ++ app/widget/timelinewidget/tool/tool.cpp | 50 +- app/widget/timelinewidget/tool/tool.h | 89 ++++ app/widget/timelinewidget/tool/transition.cpp | 25 +- app/widget/timelinewidget/tool/transition.h | 42 ++ app/widget/timelinewidget/tool/zoom.cpp | 25 +- app/widget/timelinewidget/tool/zoom.h | 41 ++ .../view/timelineviewghostitem.cpp | 30 +- .../view/timelineviewghostitem.h | 24 +- 33 files changed, 1103 insertions(+), 602 deletions(-) create mode 100644 app/widget/timelinewidget/tool/add.h create mode 100644 app/widget/timelinewidget/tool/beam.h create mode 100644 app/widget/timelinewidget/tool/edit.h create mode 100644 app/widget/timelinewidget/tool/import.h create mode 100644 app/widget/timelinewidget/tool/pointer.h create mode 100644 app/widget/timelinewidget/tool/razor.h create mode 100644 app/widget/timelinewidget/tool/ripple.h create mode 100644 app/widget/timelinewidget/tool/rolling.h create mode 100644 app/widget/timelinewidget/tool/slide.h create mode 100644 app/widget/timelinewidget/tool/slip.h create mode 100644 app/widget/timelinewidget/tool/tool.h create mode 100644 app/widget/timelinewidget/tool/transition.h create mode 100644 app/widget/timelinewidget/tool/zoom.h diff --git a/app/config/config.cpp b/app/config/config.cpp index f1dbe18fe..f2db463d7 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -87,7 +87,7 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("AutoSelectDivider"), NodeParam::kBoolean, true); SetEntryInternal(QStringLiteral("SetNameWithMarker"), NodeParam::kBoolean, false); SetEntryInternal(QStringLiteral("RectifiedWaveforms"), NodeParam::kBoolean, false); - SetEntryInternal(QStringLiteral("DropWithoutSequenceBehavior"), NodeParam::kInt, TimelineWidget::kDWSAsk); + SetEntryInternal(QStringLiteral("DropWithoutSequenceBehavior"), NodeParam::kInt, ImportTool::kDWSAsk); SetEntryInternal(QStringLiteral("Loop"), NodeParam::kBoolean, false); SetEntryInternal(QStringLiteral("AutoCacheInterval"), NodeParam::kInt, 250); diff --git a/app/widget/timelinewidget/timelinescaledobject.h b/app/widget/timelinewidget/timelinescaledobject.h index c4aec301c..3b05f8e26 100644 --- a/app/widget/timelinewidget/timelinescaledobject.h +++ b/app/widget/timelinewidget/timelinescaledobject.h @@ -48,10 +48,10 @@ public: 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); +protected: virtual void TimebaseChangedEvent(const rational&){} virtual void ScaleChangedEvent(const double&){} diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index b21bf6092..a732e4c0d 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -30,6 +30,17 @@ #include "common/timecodefunctions.h" #include "dialog/sequence/sequence.h" #include "node/block/transition/transition.h" +#include "tool/add.h" +#include "tool/beam.h" +#include "tool/edit.h" +#include "tool/pointer.h" +#include "tool/razor.h" +#include "tool/ripple.h" +#include "tool/rolling.h" +#include "tool/slide.h" +#include "tool/slip.h" +#include "tool/transition.h" +#include "tool/zoom.h" #include "tool/tool.h" #include "trackview/trackview.h" #include "widget/menu/menu.h" @@ -354,22 +365,6 @@ void TimelineWidget::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, v } } -TimelineWidget::DraggedFootage TimelineWidget::FootageToDraggedFootage(Footage *f) -{ - return DraggedFootage(f, f->get_enabled_stream_flags()); -} - -QList TimelineWidget::FootageToDraggedFootage(QList footage) -{ - QList df; - - foreach (Footage* f, footage) { - df.append(FootageToDraggedFootage(f)); - } - - return df; -} - rational TimelineWidget::GetToolTipTimebase() const { if (GetConnectedNode() && use_audio_time_units_) { @@ -843,12 +838,7 @@ void TimelineWidget::ClearGhosts() HideSnaps(); } -bool TimelineWidget::HasGhosts() -{ - return !ghost_items_.isEmpty(); -} - -TimelineWidget::Tool *TimelineWidget::GetActiveTool() +TimelineTool *TimelineWidget::GetActiveTool() { return tools_.at(Core::instance()->tool()); } @@ -875,7 +865,7 @@ void TimelineWidget::ViewMouseMoved(TimelineViewMouseEvent *event) active_tool_->MouseMove(event); } else { // Mouse is not down, attempt a hover event - Tool* hover_tool = GetActiveTool(); + TimelineTool* hover_tool = GetActiveTool(); if (hover_tool) { hover_tool->HoverMove(event); @@ -1223,6 +1213,24 @@ void TimelineWidget::SetBlockLinksSelected(Block* block, bool selected) } } +void TimelineWidget::QueueScroll(int value) +{ + // (using a hacky singleShot so the scroll occurs after the scene and its scrollbars have updated) + deferred_scroll_value_ = value; + + QTimer::singleShot(0, this, &TimelineWidget::DeferredScrollAction); +} + +TimelineView *TimelineWidget::GetFirstTimelineView() +{ + return views_.first()->view(); +} + +const QRect& TimelineWidget::GetRubberBandGeometry() const +{ + return rubberband_.geometry(); +} + QVector TimelineWidget::GetEditToInfo(const rational& playhead_time, Timeline::MovementMode mode) { @@ -1519,6 +1527,40 @@ void TimelineWidget::RemoveSelection(TimelineViewBlockItem *item) RemoveSelection(item->block()->range(), item->Track()); } +void TimelineWidget::ShiftSelections(const rational &diff) +{ + for (auto it=selections_.begin(); it!=selections_.end(); it++) { + for (auto it2=it.value().begin(); it2!=it.value().end(); it2++) { + (*it2) += diff; + } + } +} + +void TimelineWidget::SetSelections(const TimelineWidget::Selections &s) +{ + selections_ = s; + + foreach (TimelineAndTrackView* tview, views_) { + tview->view()->viewport(),update(); + } +} + +TimelineViewBlockItem *TimelineWidget::GetItemAtScenePos(const TimelineCoordinate& coord) +{ + for (auto it=block_items_.cbegin(); it!=block_items_.cend(); it++) { + Block* b = it.key(); + TimelineViewBlockItem* item = it.value(); + + if (b->in() <= coord.GetFrame() + && b->out() > coord.GetFrame() + && item->Track() == coord.GetTrack()) { + return item; + } + } + + return nullptr; +} + bool TimelineWidget::IsItemSelected(TimelineViewBlockItem *item) const { return selections_[item->Track()].ContainsTimeRange(item->block()->range()); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index f7121af03..89d91633e 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -34,6 +34,8 @@ #include "widget/nodecopypaste/nodecopypaste.h" #include "widget/slider/timeslider.h" #include "widget/timebased/timebased.h" +#include "widget/timelinewidget/tool/import.h" +#include "widget/timelinewidget/tool/tool.h" OLIVE_NAMESPACE_ENTER @@ -46,13 +48,6 @@ class TimelineWidget : public TimeBasedWidget, public NodeCopyPasteWidget, publi { Q_OBJECT public: - enum DropWithoutSequenceBehavior { - kDWSAsk, - kDWSAuto, - kDWSManual, - kDWSDisable - }; - TimelineWidget(QWidget* parent = nullptr); virtual ~TimelineWidget() override; @@ -105,6 +100,75 @@ public: static void ReplaceBlocksWithGaps(const QList& blocks, bool remove_from_graph, QUndoCommand* command); + /** + * @brief Retrieve the QGraphicsItem at a particular scene position + * + * Requires a float-based scene position. If you have a screen position, use GetScenePos() first to convert it to a + * scene position + */ + TimelineViewBlockItem* GetItemAtScenePos(const TimelineCoordinate &coord); + + const QMap& GetBlockItems() const + { + return block_items_; + } + + using Selections = QHash; + + void AddSelection(const TimeRange& time, const TrackReference& track); + void AddSelection(TimelineViewBlockItem* item); + + void RemoveSelection(const TimeRange& time, const TrackReference& track); + void RemoveSelection(TimelineViewBlockItem* item); + + void ShiftSelections(const rational& diff); + + const Selections& GetSelections() const + { + return selections_; + } + + void SetSelections(const Selections& s); + + TrackOutput* GetTrackFromReference(const TrackReference& ref); + + void SetViewBeamCursor(const TimelineCoordinate& coord); + + const QVector& GetGhostItems() const + { + return ghost_items_; + } + + void InsertGapsAt(const rational& time, const rational& length, QUndoCommand* command); + + void StartRubberBandSelect(bool enable_selecting, bool select_links); + void MoveRubberBandSelect(bool enable_selecting, bool select_links); + void EndRubberBandSelect(); + + int GetTrackY(const TrackReference& ref); + int GetTrackHeight(const TrackReference& ref); + + void AddGhost(TimelineViewGhostItem* ghost); + + void ClearGhosts(); + + bool HasGhosts() const + { + return !ghost_items_.isEmpty(); + } + + rational GetToolTipTimebase() const; + + bool IsItemSelected(TimelineViewBlockItem* item) const; + + void SetBlockLinksSelected(Block *block, bool selected); + + void QueueScroll(int value); + + TimelineView* GetFirstTimelineView(); + + const QRect &GetRubberBandGeometry() const; + signals: void BlocksSelected(const QList& selected_blocks); @@ -131,346 +195,6 @@ protected: }; private: - class DraggedFootage { - public: - DraggedFootage(Footage* f, quint64 streams) : - footage_(f), - streams_(streams) - { - } - - Footage* footage() const { - return footage_; - } - - const quint64& streams() const { - return streams_; - } - - private: - Footage* footage_; - - quint64 streams_; - - }; - - static DraggedFootage FootageToDraggedFootage(Footage* f); - static QList FootageToDraggedFootage(QList footage); - - class Tool - { - public: - Tool(TimelineWidget* parent); - virtual ~Tool(); - - virtual void MousePress(TimelineViewMouseEvent *){} - virtual void MouseMove(TimelineViewMouseEvent *){} - virtual void MouseRelease(TimelineViewMouseEvent *){} - virtual void MouseDoubleClick(TimelineViewMouseEvent *){} - - virtual void HoverMove(TimelineViewMouseEvent *){} - - virtual void DragEnter(TimelineViewMouseEvent *){} - virtual void DragMove(TimelineViewMouseEvent *){} - virtual void DragLeave(QDragLeaveEvent *){} - virtual void DragDrop(TimelineViewMouseEvent *){} - - TimelineWidget* parent(); - - static Timeline::MovementMode FlipTrimMode(const Timeline::MovementMode& trim_mode); - - protected: - /** - * @brief Retrieve the QGraphicsItem at a particular scene position - * - * Requires a float-based scene position. If you have a screen position, use GetScenePos() first to convert it to a - * scene position - */ - TimelineViewBlockItem* GetItemAtScenePos(const TimelineCoordinate &coord); - - /** - * @brief Validates Ghosts that are moving horizontally (time-based) - * - * Validation is the process of ensuring that whatever movements the user is making are "valid" and "legal". This - * function's validation ensures that no Ghost's in point ends up in a negative timecode. - */ - rational ValidateTimeMovement(rational movement); - - /** - * @brief Validates Ghosts that are moving vertically (track-based) - * - * This function's validation ensures that no Ghost's track ends up in a negative (non-existent) track. - */ - int ValidateTrackMovement(int movement, const QVector &ghosts); - - void GetGhostData(rational *earliest_point, rational *latest_point); - - void InsertGapsAtGhostDestination(QUndoCommand* command); - - QList snap_points_; - - bool dragging_; - - TimelineCoordinate drag_start_; - - private: - TimelineWidget* parent_; - - }; - - class BeamTool : public Tool - { - public: - BeamTool(TimelineWidget *parent); - - virtual void HoverMove(TimelineViewMouseEvent *event) override; - - protected: - TimelineCoordinate ValidatedCoordinate(TimelineCoordinate coord); - - }; - - class PointerTool : public Tool - { - public: - PointerTool(TimelineWidget* parent); - - virtual void MousePress(TimelineViewMouseEvent *event) override; - virtual void MouseMove(TimelineViewMouseEvent *event) override; - virtual void MouseRelease(TimelineViewMouseEvent *event) override; - - virtual void HoverMove(TimelineViewMouseEvent *event) override; - - protected: - virtual void FinishDrag(TimelineViewMouseEvent *event); - - virtual void InitiateDrag(TimelineViewBlockItem* clicked_item, - Timeline::MovementMode trim_mode); - - TimelineViewGhostItem* AddGhostFromBlock(Block *block, const TrackReference& track, Timeline::MovementMode mode, bool check_if_exists = false); - - TimelineViewGhostItem* AddGhostFromNull(const rational& in, const rational& out, const TrackReference& track, Timeline::MovementMode mode); - - /** - * @brief Validates Ghosts that are getting their in points trimmed - * - * Assumes ghost->data() is a Block. Ensures no Ghost's in point becomes a negative timecode. Also ensures no - * Ghost's length becomes 0 or negative. - */ - rational ValidateInTrimming(rational movement); - - /** - * @brief Validates Ghosts that are getting their out points trimmed - * - * Assumes ghost->data() is a Block. Ensures no Ghost's in point becomes a negative timecode. Also ensures no - * Ghost's length becomes 0 or negative. - */ - rational ValidateOutTrimming(rational movement); - - virtual void ProcessDrag(const TimelineCoordinate &mouse_pos); - - void InitiateDragInternal(TimelineViewBlockItem* clicked_item, - Timeline::MovementMode trim_mode, - bool dont_roll_trims, - bool allow_nongap_rolling, bool slide_instead_of_moving); - - const Timeline::MovementMode& drag_movement_mode() const - { - return drag_movement_mode_; - } - - void SetMovementAllowed(bool e) - { - movement_allowed_ = e; - } - - void SetTrimmingAllowed(bool e) - { - trimming_allowed_ = e; - } - - void SetTrackMovementAllowed(bool e) - { - track_movement_allowed_ = e; - } - - void SetGapTrimmingAllowed(bool e) - { - gap_trimming_allowed_ = e; - } - - private: - Timeline::MovementMode IsCursorInTrimHandle(TimelineViewBlockItem* block, qreal cursor_x); - - void AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode); - - bool IsClipTrimmable(TimelineViewBlockItem* clip, - const QList& items, - const Timeline::MovementMode& mode); - - void ProcessGhostsForSliding(); - - void ProcessGhostsForRolling(); - - bool AddMovingTransitionsToClipGhost(Block *block, const TrackReference &track, Timeline::MovementMode movement, const QList &selected_items); - - bool movement_allowed_; - bool trimming_allowed_; - bool track_movement_allowed_; - bool gap_trimming_allowed_; - bool rubberband_selecting_; - - Timeline::TrackType drag_track_type_; - Timeline::MovementMode drag_movement_mode_; - - TimelineViewBlockItem* clicked_item_; - - }; - - class ImportTool : public Tool - { - public: - ImportTool(TimelineWidget* parent); - - virtual void DragEnter(TimelineViewMouseEvent *event) override; - virtual void DragMove(TimelineViewMouseEvent *event) override; - virtual void DragLeave(QDragLeaveEvent *event) override; - virtual void DragDrop(TimelineViewMouseEvent *event) override; - - void PlaceAt(const QList &footage, const rational& start, bool insert); - void PlaceAt(const QList &footage, const rational& start, bool insert); - - private: - void FootageToGhosts(rational ghost_start, const QList& footage, const rational &dest_tb, const int &track_start); - - void PrepGhosts(const rational &frame, const int &track_index); - - void DropGhosts(bool insert); - - QList dragged_footage_; - - int import_pre_buffer_; - - }; - - class EditTool : public BeamTool - { - public: - EditTool(TimelineWidget* parent); - - virtual void MousePress(TimelineViewMouseEvent *event) override; - virtual void MouseMove(TimelineViewMouseEvent *event) override; - virtual void MouseRelease(TimelineViewMouseEvent *event) override; - virtual void MouseDoubleClick(TimelineViewMouseEvent *event) override; - - private: - QHash start_selections_; - - TimelineCoordinate start_coord_; - - }; - - class RazorTool : public BeamTool - { - public: - RazorTool(TimelineWidget* parent); - - virtual void MousePress(TimelineViewMouseEvent *event) override; - virtual void MouseMove(TimelineViewMouseEvent *event) override; - virtual void MouseRelease(TimelineViewMouseEvent *event) override; - - private: - QVector split_tracks_; - }; - - class RippleTool : public PointerTool - { - public: - RippleTool(TimelineWidget* parent); - protected: - virtual void FinishDrag(TimelineViewMouseEvent *event) override; - - virtual void InitiateDrag(TimelineViewBlockItem* clicked_item, - Timeline::MovementMode trim_mode) override; - }; - - class RollingTool : public PointerTool - { - public: - RollingTool(TimelineWidget* parent); - - protected: - virtual void InitiateDrag(TimelineViewBlockItem* clicked_item, - Timeline::MovementMode trim_mode) override; - }; - - class SlideTool : public PointerTool - { - public: - SlideTool(TimelineWidget* parent); - - protected: - virtual void InitiateDrag(TimelineViewBlockItem* clicked_item, - Timeline::MovementMode trim_mode) override; - - }; - - class SlipTool : public PointerTool - { - public: - SlipTool(TimelineWidget* parent); - - protected: - virtual void ProcessDrag(const TimelineCoordinate &mouse_pos) override; - virtual void FinishDrag(TimelineViewMouseEvent *event) override; - }; - - class ZoomTool : public Tool - { - public: - ZoomTool(TimelineWidget* parent); - - virtual void MousePress(TimelineViewMouseEvent *event) override; - virtual void MouseMove(TimelineViewMouseEvent *event) override; - virtual void MouseRelease(TimelineViewMouseEvent *event) override; - - }; - - class AddTool : public BeamTool - { - public: - AddTool(TimelineWidget* parent); - - virtual void MousePress(TimelineViewMouseEvent *event) override; - virtual void MouseMove(TimelineViewMouseEvent *event) override; - virtual void MouseRelease(TimelineViewMouseEvent *event) override; - - protected: - void MouseMoveInternal(const rational& cursor_frame, bool outwards); - - TimelineViewGhostItem* ghost_; - - rational drag_start_point_; - }; - - class TransitionTool : public AddTool - { - public: - TransitionTool(TimelineWidget* parent); - - virtual void MousePress(TimelineViewMouseEvent *event) override; - virtual void MouseMove(TimelineViewMouseEvent *event) override; - virtual void MouseRelease(TimelineViewMouseEvent *event) override; - private: - bool dual_transition_; - }; - - rational GetToolTipTimebase() const; - - void InsertGapsAt(const rational& time, const rational& length, QUndoCommand* command); - - void SetBlockLinksSelected(Block *block, bool selected); - QVector GetEditToInfo(const rational &playhead_time, Timeline::MovementMode mode); void RippleTo(Timeline::MovementMode mode); @@ -481,41 +205,24 @@ private: QPoint drag_origin_; - void StartRubberBandSelect(bool enable_selecting, bool select_links); - void MoveRubberBandSelect(bool enable_selecting, bool select_links); - void EndRubberBandSelect(); QRubberBand rubberband_; QList rubberband_already_selected_; QList rubberband_now_selected_; - QHash selections_; + Selections selections_; - void AddSelection(const TimeRange& time, const TrackReference& track); - void AddSelection(TimelineViewBlockItem* item); + TimelineTool* GetActiveTool(); - void RemoveSelection(const TimeRange& time, const TrackReference& track); - void RemoveSelection(TimelineViewBlockItem* item); - - bool IsItemSelected(TimelineViewBlockItem* item) const; - - Tool* GetActiveTool(); - - QVector tools_; + QVector tools_; ImportTool* import_tool_; - Tool* active_tool_; - - void ClearGhosts(); - - bool HasGhosts(); + TimelineTool* active_tool_; QVector ghost_items_; QMap block_items_; - TrackOutput* GetTrackFromReference(const TrackReference& ref); - QList views_; TimeSlider* timecode_label_; @@ -526,17 +233,10 @@ private: QSplitter* view_splitter_; - int GetTrackY(const TrackReference& ref); - int GetTrackHeight(const TrackReference& ref); - void CenterOn(qreal scene_pos); - void AddGhost(TimelineViewGhostItem* ghost); - void UpdateViewTimebases(); - void SetViewBeamCursor(const TimelineCoordinate& coord); - private slots: void ViewMousePressed(TimelineViewMouseEvent* event); void ViewMouseMoved(TimelineViewMouseEvent* event); diff --git a/app/widget/timelinewidget/tool/CMakeLists.txt b/app/widget/timelinewidget/tool/CMakeLists.txt index f14d4a8de..4e9850fc0 100644 --- a/app/widget/timelinewidget/tool/CMakeLists.txt +++ b/app/widget/timelinewidget/tool/CMakeLists.txt @@ -17,17 +17,30 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} widget/timelinewidget/tool/add.cpp + widget/timelinewidget/tool/add.h widget/timelinewidget/tool/beam.cpp + widget/timelinewidget/tool/beam.h widget/timelinewidget/tool/edit.cpp + widget/timelinewidget/tool/edit.h widget/timelinewidget/tool/import.cpp + widget/timelinewidget/tool/import.h widget/timelinewidget/tool/pointer.cpp + widget/timelinewidget/tool/pointer.h widget/timelinewidget/tool/razor.cpp + widget/timelinewidget/tool/razor.h widget/timelinewidget/tool/ripple.cpp + widget/timelinewidget/tool/ripple.h widget/timelinewidget/tool/rolling.cpp + widget/timelinewidget/tool/rolling.h widget/timelinewidget/tool/slide.cpp + widget/timelinewidget/tool/slide.h widget/timelinewidget/tool/slip.cpp + widget/timelinewidget/tool/slip.h widget/timelinewidget/tool/transition.cpp + widget/timelinewidget/tool/transition.h widget/timelinewidget/tool/tool.cpp + widget/timelinewidget/tool/tool.h widget/timelinewidget/tool/zoom.cpp + widget/timelinewidget/tool/zoom.h PARENT_SCOPE ) diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index f488bbfca..eefbf6e37 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -20,6 +20,7 @@ #include "widget/timelinewidget/timelinewidget.h" +#include "add.h" #include "core.h" #include "node/factory.h" #include "node/generator/solid/solid.h" @@ -28,13 +29,13 @@ OLIVE_NAMESPACE_ENTER -TimelineWidget::AddTool::AddTool(TimelineWidget *parent) : +AddTool::AddTool(TimelineWidget *parent) : BeamTool(parent), ghost_(nullptr) { } -void TimelineWidget::AddTool::MousePress(TimelineViewMouseEvent *event) +void AddTool::MousePress(TimelineViewMouseEvent *event) { const TrackReference& track = event->GetTrack(); @@ -78,7 +79,7 @@ void TimelineWidget::AddTool::MousePress(TimelineViewMouseEvent *event) } } -void TimelineWidget::AddTool::MouseMove(TimelineViewMouseEvent *event) +void AddTool::MouseMove(TimelineViewMouseEvent *event) { if (!ghost_) { return; @@ -87,16 +88,16 @@ void TimelineWidget::AddTool::MouseMove(TimelineViewMouseEvent *event) MouseMoveInternal(event->GetFrame(), event->GetModifiers() & Qt::AltModifier); } -void TimelineWidget::AddTool::MouseRelease(TimelineViewMouseEvent *event) +void AddTool::MouseRelease(TimelineViewMouseEvent *event) { const TrackReference& track = ghost_->Track(); if (ghost_) { - if (!ghost_->AdjustedLength().isNull()) { + if (!ghost_->GetAdjustedLength().isNull()) { QUndoCommand* command = new QUndoCommand(); ClipBlock* clip = new ClipBlock(); - clip->set_length_and_media_out(ghost_->AdjustedLength()); + clip->set_length_and_media_out(ghost_->GetAdjustedLength()); clip->SetLabel(OLIVE_NAMESPACE::Tool::GetAddableObjectName(Core::instance()->GetSelectedAddableObject())); NodeGraph* graph = static_cast(parent()->GetConnectedNode()->parent()); @@ -156,14 +157,14 @@ void TimelineWidget::AddTool::MouseRelease(TimelineViewMouseEvent *event) } } -void TimelineWidget::AddTool::MouseMoveInternal(const rational &cursor_frame, bool outwards) +void AddTool::MouseMoveInternal(const rational &cursor_frame, bool outwards) { // Calculate movement rational movement = cursor_frame - drag_start_point_; // Validation: Ensure in point never goes below 0 - if (movement < -ghost_->In() || (outwards && -movement < -ghost_->In())) { - movement = -ghost_->In(); + if (movement < -ghost_->GetIn() || (outwards && -movement < -ghost_->GetIn())) { + movement = -ghost_->GetIn(); } // Snap movement diff --git a/app/widget/timelinewidget/tool/add.h b/app/widget/timelinewidget/tool/add.h new file mode 100644 index 000000000..eabbdf3c9 --- /dev/null +++ b/app/widget/timelinewidget/tool/add.h @@ -0,0 +1,47 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef ADDTIMELINETOOL_H +#define ADDTIMELINETOOL_H + +#include "beam.h" + +OLIVE_NAMESPACE_ENTER + +class AddTool : public BeamTool +{ +public: + AddTool(TimelineWidget* parent); + + virtual void MousePress(TimelineViewMouseEvent *event) override; + virtual void MouseMove(TimelineViewMouseEvent *event) override; + virtual void MouseRelease(TimelineViewMouseEvent *event) override; + +protected: + void MouseMoveInternal(const rational& cursor_frame, bool outwards); + + TimelineViewGhostItem* ghost_; + + rational drag_start_point_; +}; + +OLIVE_NAMESPACE_EXIT + +#endif // ADDTIMELINETOOL_H diff --git a/app/widget/timelinewidget/tool/beam.cpp b/app/widget/timelinewidget/tool/beam.cpp index e2fe1120e..574f2077e 100644 --- a/app/widget/timelinewidget/tool/beam.cpp +++ b/app/widget/timelinewidget/tool/beam.cpp @@ -18,21 +18,22 @@ ***/ +#include "beam.h" #include "widget/timelinewidget/timelinewidget.h" OLIVE_NAMESPACE_ENTER -TimelineWidget::BeamTool::BeamTool(TimelineWidget *parent) : - Tool(parent) +BeamTool::BeamTool(TimelineWidget *parent) : + TimelineTool(parent) { } -void TimelineWidget::BeamTool::HoverMove(TimelineViewMouseEvent *event) +void BeamTool::HoverMove(TimelineViewMouseEvent *event) { parent()->SetViewBeamCursor(ValidatedCoordinate(event->GetCoordinates(true))); } -TimelineCoordinate TimelineWidget::BeamTool::ValidatedCoordinate(TimelineCoordinate coord) +TimelineCoordinate BeamTool::ValidatedCoordinate(TimelineCoordinate coord) { if (Core::instance()->snapping()) { rational movement; diff --git a/app/widget/timelinewidget/tool/beam.h b/app/widget/timelinewidget/tool/beam.h new file mode 100644 index 000000000..7949474c2 --- /dev/null +++ b/app/widget/timelinewidget/tool/beam.h @@ -0,0 +1,42 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef BEAMTIMELINETOOL_H +#define BEAMTIMELINETOOL_H + +#include "tool.h" + +OLIVE_NAMESPACE_ENTER + +class BeamTool : public TimelineTool +{ +public: + BeamTool(TimelineWidget *parent); + + virtual void HoverMove(TimelineViewMouseEvent *event) override; + +protected: + TimelineCoordinate ValidatedCoordinate(TimelineCoordinate coord); + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // BEAMTIMELINETOOL_H diff --git a/app/widget/timelinewidget/tool/edit.cpp b/app/widget/timelinewidget/tool/edit.cpp index e8b79f312..ff8c9b9e4 100644 --- a/app/widget/timelinewidget/tool/edit.cpp +++ b/app/widget/timelinewidget/tool/edit.cpp @@ -18,30 +18,31 @@ ***/ +#include "edit.h" #include "widget/timelinewidget/timelinewidget.h" OLIVE_NAMESPACE_ENTER -TimelineWidget::EditTool::EditTool(TimelineWidget* parent) : +EditTool::EditTool(TimelineWidget* parent) : BeamTool(parent) { } -void TimelineWidget::EditTool::MousePress(TimelineViewMouseEvent *event) +void EditTool::MousePress(TimelineViewMouseEvent *event) { if (!(event->GetModifiers() & Qt::ShiftModifier)) { parent()->DeselectAll(); } } -void TimelineWidget::EditTool::MouseMove(TimelineViewMouseEvent *event) +void EditTool::MouseMove(TimelineViewMouseEvent *event) { if (dragging_) { - parent()->selections_ = start_selections_; + parent()->SetSelections(start_selections_); parent()->AddSelection(TimeRange(start_coord_.GetFrame(), event->GetFrame()), start_coord_.GetTrack()); } else { - start_selections_ = parent()->selections_; + start_selections_ = parent()->GetSelections(); dragging_ = true; @@ -60,14 +61,14 @@ void TimelineWidget::EditTool::MouseMove(TimelineViewMouseEvent *event) } } -void TimelineWidget::EditTool::MouseRelease(TimelineViewMouseEvent *event) +void EditTool::MouseRelease(TimelineViewMouseEvent *event) { dragging_ = false; } -void TimelineWidget::EditTool::MouseDoubleClick(TimelineViewMouseEvent *event) +void EditTool::MouseDoubleClick(TimelineViewMouseEvent *event) { - TimelineViewBlockItem* item = GetItemAtScenePos(event->GetCoordinates()); + TimelineViewBlockItem* item = parent()->GetItemAtScenePos(event->GetCoordinates()); if (item && !parent()->GetTrackFromReference(item->Track())->IsLocked()) { parent()->AddSelection(item); diff --git a/app/widget/timelinewidget/tool/edit.h b/app/widget/timelinewidget/tool/edit.h new file mode 100644 index 000000000..8029014db --- /dev/null +++ b/app/widget/timelinewidget/tool/edit.h @@ -0,0 +1,48 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef EDITTIMELINETOOL_H +#define EDITTIMELINETOOL_H + +#include "beam.h" +#include "tool.h" + +OLIVE_NAMESPACE_ENTER + +class EditTool : public BeamTool +{ +public: + EditTool(TimelineWidget* parent); + + virtual void MousePress(TimelineViewMouseEvent *event) override; + virtual void MouseMove(TimelineViewMouseEvent *event) override; + virtual void MouseRelease(TimelineViewMouseEvent *event) override; + virtual void MouseDoubleClick(TimelineViewMouseEvent *event) override; + +private: + QHash start_selections_; + + TimelineCoordinate start_coord_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // EDITTIMELINETOOL_H diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 9850a6eab..0d8376d09 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -60,14 +60,14 @@ Timeline::TrackType TrackTypeFromStreamType(Stream::Type stream_type) return Timeline::kTrackTypeNone; } -TimelineWidget::ImportTool::ImportTool(TimelineWidget *parent) : - Tool(parent) +ImportTool::ImportTool(TimelineWidget *parent) : + TimelineTool(parent) { // Calculate width used for importing to give ghosts a slight lead-in so the ghosts aren't right on the cursor import_pre_buffer_ = QFontMetricsWidth(parent->fontMetrics(), "HHHHHHHH"); } -void TimelineWidget::ImportTool::DragEnter(TimelineViewMouseEvent *event) +void ImportTool::DragEnter(TimelineViewMouseEvent *event) { QStringList mime_formats = event->GetMimeData()->formats(); @@ -115,7 +115,7 @@ void TimelineWidget::ImportTool::DragEnter(TimelineViewMouseEvent *event) } } -void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event) +void ImportTool::DragMove(TimelineViewMouseEvent *event) { if (!dragged_footage_.isEmpty()) { @@ -124,20 +124,20 @@ void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event) int track_movement = event->GetTrack().index() - drag_start_.GetTrack().index(); time_movement = ValidateTimeMovement(time_movement); - track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_); + track_movement = ValidateTrackMovement(track_movement, parent()->GetGhostItems()); // If snapping is enabled, check for snap points if (Core::instance()->snapping()) { parent()->SnapPoint(snap_points_, &time_movement); time_movement = ValidateTimeMovement(time_movement); - track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_); + track_movement = ValidateTrackMovement(track_movement, parent()->GetGhostItems()); } rational earliest_ghost = RATIONAL_MAX; // Move ghosts to the mouse cursor - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { ghost->SetInAdjustment(time_movement); ghost->SetOutAdjustment(time_movement); ghost->SetTrackAdjustment(track_movement); @@ -168,7 +168,7 @@ void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event) } } -void TimelineWidget::ImportTool::DragLeave(QDragLeaveEvent* event) +void ImportTool::DragLeave(QDragLeaveEvent* event) { if (!dragged_footage_.isEmpty()) { parent()->ClearGhosts(); @@ -180,7 +180,7 @@ void TimelineWidget::ImportTool::DragLeave(QDragLeaveEvent* event) } } -void TimelineWidget::ImportTool::DragDrop(TimelineViewMouseEvent *event) +void ImportTool::DragDrop(TimelineViewMouseEvent *event) { if (!dragged_footage_.isEmpty()) { DropGhosts(event->GetModifiers() & Qt::ControlModifier); @@ -191,12 +191,12 @@ void TimelineWidget::ImportTool::DragDrop(TimelineViewMouseEvent *event) } } -void TimelineWidget::ImportTool::PlaceAt(const QList &footage, const rational &start, bool insert) +void ImportTool::PlaceAt(const QList &footage, const rational &start, bool insert) { PlaceAt(FootageToDraggedFootage(footage), start, insert); } -void TimelineWidget::ImportTool::PlaceAt(const QList &footage, const rational &start, bool insert) +void ImportTool::PlaceAt(const QList &footage, const rational &start, bool insert) { dragged_footage_ = footage; @@ -208,7 +208,7 @@ void TimelineWidget::ImportTool::PlaceAt(const QList &footage, c DropGhosts(insert); } -void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QList &footage_list, const rational& dest_tb, const int& track_start) +void ImportTool::FootageToGhosts(rational ghost_start, const QList &footage_list, const rational& dest_tb, const int& track_start) { foreach (const DraggedFootage& footage, footage_list) { @@ -274,8 +274,8 @@ void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QLi ghost->SetIn(ghost_start); ghost->SetOut(ghost_start + footage_duration); - snap_points_.append(ghost->In()); - snap_points_.append(ghost->Out()); + snap_points_.append(ghost->GetIn()); + snap_points_.append(ghost->GetOut()); parent()->AddGhost(ghost); } @@ -286,7 +286,7 @@ void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QLi } } -void TimelineWidget::ImportTool::PrepGhosts(const rational& frame, const int& track_index) +void ImportTool::PrepGhosts(const rational& frame, const int& track_index) { if (parent()->GetConnectedNode()) { FootageToGhosts(frame, @@ -296,7 +296,7 @@ void TimelineWidget::ImportTool::PrepGhosts(const rational& frame, const int& tr } } -void TimelineWidget::ImportTool::DropGhosts(bool insert) +void ImportTool::DropGhosts(bool insert) { QUndoCommand* command = new QUndoCommand(); @@ -394,21 +394,21 @@ void TimelineWidget::ImportTool::DropGhosts(bool insert) if (dst_graph) { - QVector block_items(parent()->ghost_items_.size()); + QVector block_items(parent()->GetGhostItems().size()); // Check if we're inserting if (insert) { InsertGapsAtGhostDestination(command); } - for (int i=0;ighost_items_.size();i++) { - TimelineViewGhostItem* ghost = parent()->ghost_items_.at(i); + for (int i=0;iGetGhostItems().size();i++) { + TimelineViewGhostItem* ghost = parent()->GetGhostItems().at(i); StreamPtr footage_stream = ghost->data(TimelineViewGhostItem::kAttachedFootage).value(); ClipBlock* clip = new ClipBlock(); - clip->set_media_in(ghost->MediaIn()); - clip->set_length_and_media_out(ghost->Length()); + clip->set_media_in(ghost->GetMediaIn()); + clip->set_length_and_media_out(ghost->GetLength()); clip->SetLabel(footage_stream->footage()->name()); new NodeAddCommand(dst_graph, clip, command); @@ -463,7 +463,7 @@ void TimelineWidget::ImportTool::DropGhosts(bool insert) // Link any clips so far that share the same Footage with this one for (int j=0;jghost_items_.at(j)->data(TimelineViewGhostItem::kAttachedFootage).value(); + StreamPtr footage_compare = parent()->GetGhostItems().at(j)->data(TimelineViewGhostItem::kAttachedFootage).value(); if (footage_compare->footage() == footage_stream->footage()) { Block::Link(block_items.at(j), clip); @@ -482,4 +482,25 @@ void TimelineWidget::ImportTool::DropGhosts(bool insert) } } +ImportTool::DraggedFootage ImportTool::FootageToDraggedFootage(Footage *f) +{ + return DraggedFootage(f, f->get_enabled_stream_flags()); +} + +QList ImportTool::FootageToDraggedFootage(QList footage) +{ + QList df; + + foreach (Footage* f, footage) { + df.append(FootageToDraggedFootage(f)); + } + + return df; +} + +QString ImportTool::tr(const char *s) +{ + return QCoreApplication::translate("ImportTool", s); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timelinewidget/tool/import.h b/app/widget/timelinewidget/tool/import.h new file mode 100644 index 000000000..a90c82c49 --- /dev/null +++ b/app/widget/timelinewidget/tool/import.h @@ -0,0 +1,91 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef IMPORTTIMELINETOOL_H +#define IMPORTTIMELINETOOL_H + +#include "tool.h" + +OLIVE_NAMESPACE_ENTER + +class ImportTool : public TimelineTool +{ +public: + ImportTool(TimelineWidget* parent); + + virtual void DragEnter(TimelineViewMouseEvent *event) override; + virtual void DragMove(TimelineViewMouseEvent *event) override; + virtual void DragLeave(QDragLeaveEvent *event) override; + virtual void DragDrop(TimelineViewMouseEvent *event) override; + + class DraggedFootage { + public: + DraggedFootage(Footage* f, quint64 streams) : + footage_(f), + streams_(streams) + { + } + + Footage* footage() const { + return footage_; + } + + const quint64& streams() const { + return streams_; + } + + private: + Footage* footage_; + + quint64 streams_; + + }; + + void PlaceAt(const QList &footage, const rational& start, bool insert); + void PlaceAt(const QList &footage, const rational& start, bool insert); + + enum DropWithoutSequenceBehavior { + kDWSAsk, + kDWSAuto, + kDWSManual, + kDWSDisable + }; + +private: + static DraggedFootage FootageToDraggedFootage(Footage* f); + static QList FootageToDraggedFootage(QList footage); + + QString tr(const char* s); + + void FootageToGhosts(rational ghost_start, const QList& footage, const rational &dest_tb, const int &track_start); + + void PrepGhosts(const rational &frame, const int &track_index); + + void DropGhosts(bool insert); + + QList dragged_footage_; + + int import_pre_buffer_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // IMPORTTIMELINETOOL_H diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 186b8e93e..31314d133 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -32,12 +32,13 @@ #include "core.h" #include "node/block/gap/gap.h" #include "node/block/transition/transition.h" +#include "pointer.h" #include "widget/nodeview/nodeviewundo.h" OLIVE_NAMESPACE_ENTER -TimelineWidget::PointerTool::PointerTool(TimelineWidget *parent) : - Tool(parent), +PointerTool::PointerTool(TimelineWidget *parent) : + TimelineTool(parent), movement_allowed_(true), trimming_allowed_(true), track_movement_allowed_(true), @@ -46,10 +47,10 @@ TimelineWidget::PointerTool::PointerTool(TimelineWidget *parent) : { } -void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event) +void PointerTool::MousePress(TimelineViewMouseEvent *event) { // Determine if item clicked on is selectable - clicked_item_ = GetItemAtScenePos(event->GetCoordinates()); + clicked_item_ = parent()->GetItemAtScenePos(event->GetCoordinates()); bool selectable_item = (clicked_item_ && !parent()->GetTrackFromReference(clicked_item_->Track())->IsLocked()); @@ -130,7 +131,7 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event) } } -void TimelineWidget::PointerTool::MouseMove(TimelineViewMouseEvent *event) +void PointerTool::MouseMove(TimelineViewMouseEvent *event) { if (rubberband_selecting_) { // Process rubberband select @@ -154,7 +155,7 @@ void TimelineWidget::PointerTool::MouseMove(TimelineViewMouseEvent *event) } - if (dragging_ && !parent()->ghost_items_.isEmpty()) { + if (dragging_ && !parent()->GetGhostItems().isEmpty()) { // We're already dragging AND we have ghosts to work with ProcessDrag(event->GetCoordinates()); @@ -163,7 +164,7 @@ void TimelineWidget::PointerTool::MouseMove(TimelineViewMouseEvent *event) } } -void TimelineWidget::PointerTool::MouseRelease(TimelineViewMouseEvent *event) +void PointerTool::MouseRelease(TimelineViewMouseEvent *event) { if (rubberband_selecting_) { // Finish rubberband select @@ -174,7 +175,7 @@ void TimelineWidget::PointerTool::MouseRelease(TimelineViewMouseEvent *event) if (dragging_) { // If we were dragging, process the end of the drag - if (!parent()->ghost_items_.isEmpty()) { + if (!parent()->GetGhostItems().isEmpty()) { FinishDrag(event); } @@ -186,11 +187,11 @@ void TimelineWidget::PointerTool::MouseRelease(TimelineViewMouseEvent *event) } } -void TimelineWidget::PointerTool::HoverMove(TimelineViewMouseEvent *event) +void PointerTool::HoverMove(TimelineViewMouseEvent *event) { if (trimming_allowed_) { // No dragging, but we still want to process cursors - TimelineViewBlockItem* block_at_cursor = GetItemAtScenePos(event->GetCoordinates()); + TimelineViewBlockItem* block_at_cursor = parent()->GetItemAtScenePos(event->GetCoordinates()); if (block_at_cursor) { switch (IsCursorInTrimHandle(block_at_cursor, event->GetSceneX())) { @@ -217,7 +218,7 @@ void SetGhostToSlideMode(TimelineViewGhostItem* g) g->setData(TimelineViewGhostItem::kGhostIsSliding, true); } -void TimelineWidget::PointerTool::InitiateDragInternal(TimelineViewBlockItem *clicked_item, +void PointerTool::InitiateDragInternal(TimelineViewBlockItem *clicked_item, Timeline::MovementMode trim_mode, bool dont_roll_trims, bool allow_nongap_rolling, @@ -443,7 +444,7 @@ void TimelineWidget::PointerTool::InitiateDragInternal(TimelineViewBlockItem *cl } } -void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos) +void PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos) { // Calculate track movement int track_movement = track_movement_allowed_ @@ -469,7 +470,7 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po // Validate ghosts that are being moved (clips from other track types do NOT get moved) { - QVector validate_track_ghosts = parent()->ghost_items_; + QVector validate_track_ghosts = parent()->GetGhostItems(); for (int i=0;iTrack().type() != drag_track_type_) { validate_track_ghosts.removeAt(i); @@ -480,8 +481,8 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po } // Perform movement - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { - switch (ghost->mode()) { + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { + switch (ghost->GetMode()) { case Timeline::kNone: break; case Timeline::kTrimIn: @@ -524,22 +525,22 @@ struct GhostBlockPair { Block* block; }; -void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event) +void PointerTool::FinishDrag(TimelineViewMouseEvent *event) { QList blocks_moving; QList blocks_sliding; QList blocks_trimming; // Sort ghosts depending on which ones are trimming, which are moving, and which are sliding - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { if (ghost->HasBeenAdjusted()) { Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); if (ghost->data(TimelineViewGhostItem::kGhostIsSliding).toBool()) { blocks_sliding.append({ghost, b}); - } else if (ghost->mode() == Timeline::kMove) { + } else if (ghost->GetMode() == Timeline::kMove) { blocks_moving.append({ghost, b}); - } else if (Timeline::IsATrimMode(ghost->mode())) { + } else if (Timeline::IsATrimMode(ghost->GetMode())) { blocks_trimming.append({ghost, b}); } } @@ -563,8 +564,8 @@ void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event) // Must be an ordinary trim/roll BlockTrimCommand* c = new BlockTrimCommand(parent()->GetTrackFromReference(ghost->GetAdjustedTrack()), p.block, - ghost->AdjustedLength(), - ghost->mode(), + ghost->GetAdjustedLength(), + ghost->GetMode(), command); c->SetTrimIsARollEdit(ghost->data(TimelineViewGhostItem::kTrimIsARollEdit).toBool()); @@ -631,13 +632,13 @@ void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event) foreach (const GhostBlockPair& p, blocks_sliding) { const TrackReference& track = p.ghost->Track(); - switch (p.ghost->mode()) { + switch (p.ghost->GetMode()) { case Timeline::kNone: break; case Timeline::kMove: { // These all should have moved uniformly, so as long as this is set, it should be fine - movement = p.ghost->InAdjustment(); + movement = p.ghost->GetInAdjustment(); QList& blocks_on_this_track = slide_info[track]; bool inserted = false; @@ -682,7 +683,7 @@ void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event) Core::instance()->undo_stack()->pushIfHasChildren(command); } -Timeline::MovementMode TimelineWidget::PointerTool::IsCursorInTrimHandle(TimelineViewBlockItem *block, qreal cursor_x) +Timeline::MovementMode PointerTool::IsCursorInTrimHandle(TimelineViewBlockItem *block, qreal cursor_x) { double kTrimHandle = QFontMetricsWidth(parent()->fontMetrics(), "H"); @@ -700,7 +701,7 @@ Timeline::MovementMode TimelineWidget::PointerTool::IsCursorInTrimHandle(Timelin } } -void TimelineWidget::PointerTool::InitiateDrag(TimelineViewBlockItem* clicked_item, +void PointerTool::InitiateDrag(TimelineViewBlockItem* clicked_item, Timeline::MovementMode trim_mode) { InitiateDragInternal(clicked_item, trim_mode, false, false, false); @@ -708,10 +709,10 @@ void TimelineWidget::PointerTool::InitiateDrag(TimelineViewBlockItem* clicked_it //#define HIDE_GAP_GHOSTS -TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromBlock(Block* block, const TrackReference& track, Timeline::MovementMode mode, bool check_if_exists) +TimelineViewGhostItem* PointerTool::AddGhostFromBlock(Block* block, const TrackReference& track, Timeline::MovementMode mode, bool check_if_exists) { if (check_if_exists) { - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { if (Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)) == block) { return ghost; } @@ -734,7 +735,7 @@ TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromBlock(Block* blo return ghost; } -TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromNull(const rational &in, const rational &out, const TrackReference& track, Timeline::MovementMode mode) +TimelineViewGhostItem* PointerTool::AddGhostFromNull(const rational &in, const rational &out, const TrackReference& track, Timeline::MovementMode mode) { TimelineViewGhostItem* ghost = new TimelineViewGhostItem(); @@ -752,21 +753,21 @@ TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromNull(const ratio return ghost; } -void TimelineWidget::PointerTool::AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode) +void PointerTool::AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode) { ghost->SetMode(mode); // Prepare snap points (optimizes snapping for later) switch (mode) { case Timeline::kMove: - snap_points_.append(ghost->In()); - snap_points_.append(ghost->Out()); + snap_points_.append(ghost->GetIn()); + snap_points_.append(ghost->GetOut()); break; case Timeline::kTrimIn: - snap_points_.append(ghost->In()); + snap_points_.append(ghost->GetIn()); break; case Timeline::kTrimOut: - snap_points_.append(ghost->Out()); + snap_points_.append(ghost->GetOut()); break; default: break; @@ -775,7 +776,7 @@ void TimelineWidget::PointerTool::AddGhostInternal(TimelineViewGhostItem* ghost, parent()->AddGhost(ghost); } -bool TimelineWidget::PointerTool::IsClipTrimmable(TimelineViewBlockItem* clip, +bool PointerTool::IsClipTrimmable(TimelineViewBlockItem* clip, const QList& items, const Timeline::MovementMode& mode) { @@ -791,7 +792,7 @@ bool TimelineWidget::PointerTool::IsClipTrimmable(TimelineViewBlockItem* clip, return true; } -bool TimelineWidget::PointerTool::AddMovingTransitionsToClipGhost(Block* block, +bool PointerTool::AddMovingTransitionsToClipGhost(Block* block, const TrackReference& track, Timeline::MovementMode movement, const QList& selected_items) @@ -841,41 +842,41 @@ bool TimelineWidget::PointerTool::AddMovingTransitionsToClipGhost(Block* block, return ret; } -rational TimelineWidget::PointerTool::ValidateInTrimming(rational movement) +rational PointerTool::ValidateInTrimming(rational movement) { - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { - if (ghost->mode() != Timeline::kTrimIn) { + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { + if (ghost->GetMode() != Timeline::kTrimIn) { continue; } rational earliest_in = RATIONAL_MIN; - rational latest_in = ghost->Out(); + rational latest_in = ghost->GetOut(); if (!ghost->CanHaveZeroLength()) { latest_in -= parent()->timebase(); } // Clamp adjusted value between the earliest and latest values - rational adjusted = ghost->In() + movement; + rational adjusted = ghost->GetIn() + movement; rational clamped = clamp(adjusted, earliest_in, latest_in); if (clamped != adjusted) { - movement = clamped - ghost->In(); + movement = clamped - ghost->GetIn(); } } return movement; } -rational TimelineWidget::PointerTool::ValidateOutTrimming(rational movement) +rational PointerTool::ValidateOutTrimming(rational movement) { - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { - if (ghost->mode() != Timeline::kTrimOut) { + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { + if (ghost->GetMode() != Timeline::kTrimOut) { continue; } // Determine earliest and latest out points - rational earliest_out = ghost->In(); + rational earliest_out = ghost->GetIn(); if (!ghost->CanHaveZeroLength()) { earliest_out += parent()->timebase(); @@ -884,11 +885,11 @@ rational TimelineWidget::PointerTool::ValidateOutTrimming(rational movement) rational latest_out = RATIONAL_MAX; // Clamp adjusted value between the earliest and latest values - rational adjusted = ghost->Out() + movement; + rational adjusted = ghost->GetOut() + movement; rational clamped = clamp(adjusted, earliest_out, latest_out); if (clamped != adjusted) { - movement = clamped - ghost->Out(); + movement = clamped - ghost->GetOut(); } } diff --git a/app/widget/timelinewidget/tool/pointer.h b/app/widget/timelinewidget/tool/pointer.h new file mode 100644 index 000000000..31ec8b455 --- /dev/null +++ b/app/widget/timelinewidget/tool/pointer.h @@ -0,0 +1,127 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef POINTERTIMELINETOOL_H +#define POINTERTIMELINETOOL_H + +#include "tool.h" + +OLIVE_NAMESPACE_ENTER + +class PointerTool : public TimelineTool +{ +public: + PointerTool(TimelineWidget* parent); + + virtual void MousePress(TimelineViewMouseEvent *event) override; + virtual void MouseMove(TimelineViewMouseEvent *event) override; + virtual void MouseRelease(TimelineViewMouseEvent *event) override; + + virtual void HoverMove(TimelineViewMouseEvent *event) override; + +protected: + virtual void FinishDrag(TimelineViewMouseEvent *event); + + virtual void InitiateDrag(TimelineViewBlockItem* clicked_item, + Timeline::MovementMode trim_mode); + + TimelineViewGhostItem* AddGhostFromBlock(Block *block, const TrackReference& track, Timeline::MovementMode mode, bool check_if_exists = false); + + TimelineViewGhostItem* AddGhostFromNull(const rational& in, const rational& out, const TrackReference& track, Timeline::MovementMode mode); + + /** + * @brief Validates Ghosts that are getting their in points trimmed + * + * Assumes ghost->data() is a Block. Ensures no Ghost's in point becomes a negative timecode. Also ensures no + * Ghost's length becomes 0 or negative. + */ + rational ValidateInTrimming(rational movement); + + /** + * @brief Validates Ghosts that are getting their out points trimmed + * + * Assumes ghost->data() is a Block. Ensures no Ghost's in point becomes a negative timecode. Also ensures no + * Ghost's length becomes 0 or negative. + */ + rational ValidateOutTrimming(rational movement); + + virtual void ProcessDrag(const TimelineCoordinate &mouse_pos); + + void InitiateDragInternal(TimelineViewBlockItem* clicked_item, + Timeline::MovementMode trim_mode, + bool dont_roll_trims, + bool allow_nongap_rolling, bool slide_instead_of_moving); + + const Timeline::MovementMode& drag_movement_mode() const + { + return drag_movement_mode_; + } + + void SetMovementAllowed(bool e) + { + movement_allowed_ = e; + } + + void SetTrimmingAllowed(bool e) + { + trimming_allowed_ = e; + } + + void SetTrackMovementAllowed(bool e) + { + track_movement_allowed_ = e; + } + + void SetGapTrimmingAllowed(bool e) + { + gap_trimming_allowed_ = e; + } + +private: + Timeline::MovementMode IsCursorInTrimHandle(TimelineViewBlockItem* block, qreal cursor_x); + + void AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode); + + bool IsClipTrimmable(TimelineViewBlockItem* clip, + const QList& items, + const Timeline::MovementMode& mode); + + void ProcessGhostsForSliding(); + + void ProcessGhostsForRolling(); + + bool AddMovingTransitionsToClipGhost(Block *block, const TrackReference &track, Timeline::MovementMode movement, const QList &selected_items); + + bool movement_allowed_; + bool trimming_allowed_; + bool track_movement_allowed_; + bool gap_trimming_allowed_; + bool rubberband_selecting_; + + Timeline::TrackType drag_track_type_; + Timeline::MovementMode drag_movement_mode_; + + TimelineViewBlockItem* clicked_item_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // POINTERTIMELINETOOL_H diff --git a/app/widget/timelinewidget/tool/razor.cpp b/app/widget/timelinewidget/tool/razor.cpp index 49177ce8a..fb6244ee8 100644 --- a/app/widget/timelinewidget/tool/razor.cpp +++ b/app/widget/timelinewidget/tool/razor.cpp @@ -18,23 +18,24 @@ ***/ +#include "razor.h" #include "widget/timelinewidget/timelinewidget.h" OLIVE_NAMESPACE_ENTER -TimelineWidget::RazorTool::RazorTool(TimelineWidget* parent) : +RazorTool::RazorTool(TimelineWidget* parent) : BeamTool(parent) { } -void TimelineWidget::RazorTool::MousePress(TimelineViewMouseEvent *event) +void RazorTool::MousePress(TimelineViewMouseEvent *event) { split_tracks_.clear(); MouseMove(event); } -void TimelineWidget::RazorTool::MouseMove(TimelineViewMouseEvent *event) +void RazorTool::MouseMove(TimelineViewMouseEvent *event) { if (!dragging_) { drag_start_ = ValidatedCoordinate(event->GetCoordinates(true)); @@ -49,7 +50,7 @@ void TimelineWidget::RazorTool::MouseMove(TimelineViewMouseEvent *event) } } -void TimelineWidget::RazorTool::MouseRelease(TimelineViewMouseEvent *event) +void RazorTool::MouseRelease(TimelineViewMouseEvent *event) { Q_UNUSED(event) diff --git a/app/widget/timelinewidget/tool/razor.h b/app/widget/timelinewidget/tool/razor.h new file mode 100644 index 000000000..d2da08ac3 --- /dev/null +++ b/app/widget/timelinewidget/tool/razor.h @@ -0,0 +1,43 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef RAZORTIMELINETOOL_H +#define RAZORTIMELINETOOL_H + +#include "beam.h" + +OLIVE_NAMESPACE_ENTER + +class RazorTool : public BeamTool +{ +public: + RazorTool(TimelineWidget* parent); + + virtual void MousePress(TimelineViewMouseEvent *event) override; + virtual void MouseMove(TimelineViewMouseEvent *event) override; + virtual void MouseRelease(TimelineViewMouseEvent *event) override; + +private: + QVector split_tracks_; +}; + +OLIVE_NAMESPACE_EXIT + +#endif // RAZORTIMELINETOOL_H diff --git a/app/widget/timelinewidget/tool/ripple.cpp b/app/widget/timelinewidget/tool/ripple.cpp index 919c0d908..3b8273327 100644 --- a/app/widget/timelinewidget/tool/ripple.cpp +++ b/app/widget/timelinewidget/tool/ripple.cpp @@ -21,36 +21,37 @@ #include "widget/timelinewidget/timelinewidget.h" #include "node/block/gap/gap.h" +#include "ripple.h" #include "widget/nodeview/nodeviewundo.h" OLIVE_NAMESPACE_ENTER -TimelineWidget::RippleTool::RippleTool(TimelineWidget* parent) : +RippleTool::RippleTool(TimelineWidget* parent) : PointerTool(parent) { SetMovementAllowed(false); SetGapTrimmingAllowed(true); } -void TimelineWidget::RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_item, +void RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_item, Timeline::MovementMode trim_mode) { InitiateDragInternal(clicked_item, trim_mode, true, true, false); - if (parent()->ghost_items_.isEmpty()) { + if (!parent()->HasGhosts()) { return; } // Find the earliest ripple rational earliest_ripple = RATIONAL_MAX; - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { rational ghost_ripple_point; if (trim_mode == Timeline::kTrimIn) { - ghost_ripple_point = ghost->In(); + ghost_ripple_point = ghost->GetIn(); } else { - ghost_ripple_point = ghost->Out(); + ghost_ripple_point = ghost->GetOut(); } earliest_ripple = qMin(earliest_ripple, ghost_ripple_point); @@ -65,7 +66,7 @@ void TimelineWidget::RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_ite // Determine if we've already created a ghost on this track bool ghost_on_this_track_exists = false; - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { if (parent()->GetTrackFromReference(ghost->Track()) == track) { ghost_on_this_track_exists = true; break; @@ -103,20 +104,20 @@ void TimelineWidget::RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_ite } } -void TimelineWidget::RippleTool::FinishDrag(TimelineViewMouseEvent *event) +void RippleTool::FinishDrag(TimelineViewMouseEvent *event) { Q_UNUSED(event) QVector< QList > info_list(Timeline::kTrackTypeCount); - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { TrackOutput* track = parent()->GetTrackFromReference(ghost->Track()); TrackListRippleToolCommand::RippleInfo i = {Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)), Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kReferenceBlock)), track, - ghost->AdjustedLength(), - ghost->Length()}; + ghost->GetAdjustedLength(), + ghost->GetLength()}; info_list[track->track_type()].append(i); } diff --git a/app/widget/timelinewidget/tool/ripple.h b/app/widget/timelinewidget/tool/ripple.h new file mode 100644 index 000000000..bb946eaa3 --- /dev/null +++ b/app/widget/timelinewidget/tool/ripple.h @@ -0,0 +1,41 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef RIPPLETIMELINETOOL_H +#define RIPPLETIMELINETOOL_H + +#include "pointer.h" + +OLIVE_NAMESPACE_ENTER + +class RippleTool : public PointerTool +{ +public: + RippleTool(TimelineWidget* parent); +protected: + virtual void FinishDrag(TimelineViewMouseEvent *event) override; + + virtual void InitiateDrag(TimelineViewBlockItem* clicked_item, + Timeline::MovementMode trim_mode) override; +}; + +OLIVE_NAMESPACE_EXIT + +#endif // RIPPLETIMELINETOOL_H diff --git a/app/widget/timelinewidget/tool/rolling.cpp b/app/widget/timelinewidget/tool/rolling.cpp index 2aa55e43e..4f340991a 100644 --- a/app/widget/timelinewidget/tool/rolling.cpp +++ b/app/widget/timelinewidget/tool/rolling.cpp @@ -21,18 +21,19 @@ #include "widget/timelinewidget/timelinewidget.h" #include "node/block/gap/gap.h" +#include "rolling.h" #include "widget/nodeview/nodeviewundo.h" OLIVE_NAMESPACE_ENTER -TimelineWidget::RollingTool::RollingTool(TimelineWidget* parent) : +RollingTool::RollingTool(TimelineWidget* parent) : PointerTool(parent) { SetMovementAllowed(false); SetGapTrimmingAllowed(true); } -void TimelineWidget::RollingTool::InitiateDrag(TimelineViewBlockItem *clicked_item, +void RollingTool::InitiateDrag(TimelineViewBlockItem *clicked_item, Timeline::MovementMode trim_mode) { InitiateDragInternal(clicked_item, trim_mode, false, true, false); diff --git a/app/widget/timelinewidget/tool/rolling.h b/app/widget/timelinewidget/tool/rolling.h new file mode 100644 index 000000000..f804b9118 --- /dev/null +++ b/app/widget/timelinewidget/tool/rolling.h @@ -0,0 +1,40 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef ROLLINGTIMELINETOOL_H +#define ROLLINGTIMELINETOOL_H + +#include "pointer.h" + +OLIVE_NAMESPACE_ENTER + +class RollingTool : public PointerTool +{ +public: + RollingTool(TimelineWidget* parent); + +protected: + virtual void InitiateDrag(TimelineViewBlockItem* clicked_item, + Timeline::MovementMode trim_mode) override; +}; + +OLIVE_NAMESPACE_EXIT + +#endif // ROLLINGTIMELINETOOL_H diff --git a/app/widget/timelinewidget/tool/slide.cpp b/app/widget/timelinewidget/tool/slide.cpp index c64d9ec43..0dc7d1482 100644 --- a/app/widget/timelinewidget/tool/slide.cpp +++ b/app/widget/timelinewidget/tool/slide.cpp @@ -21,11 +21,12 @@ #include "widget/timelinewidget/timelinewidget.h" #include "node/block/gap/gap.h" +#include "slide.h" #include "widget/nodeview/nodeviewundo.h" OLIVE_NAMESPACE_ENTER -TimelineWidget::SlideTool::SlideTool(TimelineWidget* parent) : +SlideTool::SlideTool(TimelineWidget* parent) : PointerTool(parent) { SetTrimmingAllowed(false); @@ -33,7 +34,7 @@ TimelineWidget::SlideTool::SlideTool(TimelineWidget* parent) : SetGapTrimmingAllowed(true); } -void TimelineWidget::SlideTool::InitiateDrag(TimelineViewBlockItem *clicked_item, +void SlideTool::InitiateDrag(TimelineViewBlockItem *clicked_item, Timeline::MovementMode trim_mode) { InitiateDragInternal(clicked_item, trim_mode, false, true, true); diff --git a/app/widget/timelinewidget/tool/slide.h b/app/widget/timelinewidget/tool/slide.h new file mode 100644 index 000000000..326f78d8b --- /dev/null +++ b/app/widget/timelinewidget/tool/slide.h @@ -0,0 +1,41 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef SLIDETIMELINETOOL_H +#define SLIDETIMELINETOOL_H + +#include "pointer.h" + +OLIVE_NAMESPACE_ENTER + +class SlideTool : public PointerTool +{ +public: + SlideTool(TimelineWidget* parent); + +protected: + virtual void InitiateDrag(TimelineViewBlockItem* clicked_item, + Timeline::MovementMode trim_mode) override; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // SLIDETIMELINETOOL_H diff --git a/app/widget/timelinewidget/tool/slip.cpp b/app/widget/timelinewidget/tool/slip.cpp index 0208743be..caea2014d 100644 --- a/app/widget/timelinewidget/tool/slip.cpp +++ b/app/widget/timelinewidget/tool/slip.cpp @@ -24,30 +24,31 @@ #include "common/timecodefunctions.h" #include "config/config.h" +#include "slip.h" OLIVE_NAMESPACE_ENTER -TimelineWidget::SlipTool::SlipTool(TimelineWidget *parent) : +SlipTool::SlipTool(TimelineWidget *parent) : PointerTool(parent) { SetTrimmingAllowed(false); SetTrackMovementAllowed(false); } -void TimelineWidget::SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos) +void SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos) { // Determine frame movement rational time_movement = drag_start_.GetFrame() - mouse_pos.GetFrame(); // Validate slip (enforce all ghosts moving in legal ways) - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { - if (ghost->MediaIn() + time_movement < 0) { - time_movement = -ghost->MediaIn(); + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { + if (ghost->GetMediaIn() + time_movement < 0) { + time_movement = -ghost->GetMediaIn(); } } // Perform slip - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { ghost->SetMediaInAdjustment(time_movement); } @@ -62,14 +63,14 @@ void TimelineWidget::SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos) parent()); } -void TimelineWidget::SlipTool::FinishDrag(TimelineViewMouseEvent *event) +void SlipTool::FinishDrag(TimelineViewMouseEvent *event) { Q_UNUSED(event) QUndoCommand* command = new QUndoCommand(); // Find earliest point to ripple around - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); new BlockSetMediaInCommand(b, ghost->GetAdjustedMediaIn(), command); diff --git a/app/widget/timelinewidget/tool/slip.h b/app/widget/timelinewidget/tool/slip.h new file mode 100644 index 000000000..bf358e5fc --- /dev/null +++ b/app/widget/timelinewidget/tool/slip.h @@ -0,0 +1,40 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef SLIPTIMELINETOOL_H +#define SLIPTIMELINETOOL_H + +#include "pointer.h" + +OLIVE_NAMESPACE_ENTER + +class SlipTool : public PointerTool +{ +public: + SlipTool(TimelineWidget* parent); + +protected: + virtual void ProcessDrag(const TimelineCoordinate &mouse_pos) override; + virtual void FinishDrag(TimelineViewMouseEvent *event) override; +}; + +OLIVE_NAMESPACE_EXIT + +#endif // SLIPTIMELINETOOL_H diff --git a/app/widget/timelinewidget/tool/tool.cpp b/app/widget/timelinewidget/tool/tool.cpp index 04ce25b32..060c82da1 100644 --- a/app/widget/timelinewidget/tool/tool.cpp +++ b/app/widget/timelinewidget/tool/tool.cpp @@ -25,22 +25,22 @@ OLIVE_NAMESPACE_ENTER -TimelineWidget::Tool::Tool(TimelineWidget *parent) : +TimelineTool::TimelineTool(TimelineWidget *parent) : dragging_(false), parent_(parent) { } -TimelineWidget::Tool::~Tool() +TimelineTool::~TimelineTool() { } -TimelineWidget *TimelineWidget::Tool::parent() +TimelineWidget *TimelineTool::parent() { return parent_; } -Timeline::MovementMode TimelineWidget::Tool::FlipTrimMode(const Timeline::MovementMode &trim_mode) +Timeline::MovementMode TimelineTool::FlipTrimMode(const Timeline::MovementMode &trim_mode) { if (trim_mode == Timeline::kTrimIn) { return Timeline::kTrimOut; @@ -53,50 +53,30 @@ Timeline::MovementMode TimelineWidget::Tool::FlipTrimMode(const Timeline::Moveme return trim_mode; } -TimelineViewBlockItem *TimelineWidget::Tool::GetItemAtScenePos(const TimelineCoordinate& coord) +rational TimelineTool::ValidateTimeMovement(rational movement) { - QMapIterator iterator(parent()->block_items_); - - while (iterator.hasNext()) { - iterator.next(); - - Block* b = iterator.key(); - TimelineViewBlockItem* item = iterator.value(); - - if (b->in() <= coord.GetFrame() - && b->out() > coord.GetFrame() - && item->Track() == coord.GetTrack()) { - return item; - } - } - - return nullptr; -} - -rational TimelineWidget::Tool::ValidateTimeMovement(rational movement) -{ - foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { - if (ghost->mode() != Timeline::kMove) { + foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) { + if (ghost->GetMode() != Timeline::kMove) { continue; } // Prevents any ghosts from going below 0:00:00 time - if (ghost->In() + movement < 0) { - movement = -ghost->In(); + if (ghost->GetIn() + movement < 0) { + movement = -ghost->GetIn(); } } return movement; } -int TimelineWidget::Tool::ValidateTrackMovement(int movement, const QVector& ghosts) +int TimelineTool::ValidateTrackMovement(int movement, const QVector& ghosts) { foreach (TimelineViewGhostItem* ghost, ghosts) { - if (ghost->mode() != Timeline::kMove) { + if (ghost->GetMode() != Timeline::kMove) { continue; } - if (!ghost->CanMoveTracks()) { + if (!ghost->GetCanMoveTracks()) { return 0; @@ -111,12 +91,12 @@ int TimelineWidget::Tool::ValidateTrackMovement(int movement, const QVector