diff --git a/CMakeLists.txt b/CMakeLists.txt index d93aaaa9d..9c86f9c02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ cmake_minimum_required(VERSION 3.9 FATAL_ERROR) -project(olive-editor VERSION 0.1.0 LANGUAGES CXX) +project(olive-editor VERSION 0.2.0 LANGUAGES CXX) option(UPDATE_TS "Update translations" OFF) option(BUILD_DOXYGEN "Build Doxygen documentation" OFF) diff --git a/app/node/output/timeline/timeline.cpp b/app/node/output/timeline/timeline.cpp index 556a8a30b..fffb802e2 100644 --- a/app/node/output/timeline/timeline.cpp +++ b/app/node/output/timeline/timeline.cpp @@ -29,9 +29,13 @@ TimelineOutput::TimelineOutput() { - track_input_ = new NodeInput("track_in"); - track_input_->add_data_input(NodeParam::kTrack); - AddParameter(track_input_); + video_track_input_ = new NodeInput("video_track_in"); + video_track_input_->add_data_input(NodeParam::kTrack); + AddParameter(video_track_input_); + + audio_track_input_ = new NodeInput("audio_track_in"); + audio_track_input_->add_data_input(NodeParam::kTrack); + AddParameter(audio_track_input_); length_output_ = new NodeOutput("length_out"); length_output_->set_data_type(NodeParam::kRational); @@ -68,7 +72,7 @@ const rational &TimelineOutput::Timebase() NodeInput *TimelineOutput::track_input() { - return track_input_; + return video_track_input_; } NodeOutput *TimelineOutput::length_output() @@ -91,20 +95,13 @@ QVariant TimelineOutput::Value(NodeOutput *output, const rational &time) if (output == length_output_) { Q_UNUSED(time) - rational length; - - // Retrieve each track's end point and return the longest of the tracks - foreach (TrackOutput* track, track_cache_) { - length = qMax(track->in(), length); - } - - return QVariant::fromValue(length); + return QVariant::fromValue(TimelineLength()); } return 0; } -rational TimelineOutput::GetSequenceLength() +rational TimelineOutput::TimelineLength() { rational length = 0; @@ -117,7 +114,7 @@ rational TimelineOutput::GetSequenceLength() TrackOutput *TimelineOutput::attached_track() { - return ValueToPtr(track_input_->get_value(0)); + return ValueToPtr(video_track_input_->get_value(0)); } void TimelineOutput::AttachTrack(TrackOutput *track) diff --git a/app/node/output/timeline/timeline.h b/app/node/output/timeline/timeline.h index a9bae133c..d98f8bcbc 100644 --- a/app/node/output/timeline/timeline.h +++ b/app/node/output/timeline/timeline.h @@ -32,6 +32,12 @@ class TimelineOutput : public Node { Q_OBJECT public: + enum TrackType { + kVideo, + kAudio, + kSubtitle + }; + TimelineOutput(); virtual QString Name() override; @@ -54,6 +60,8 @@ public: void RemoveTrack(); + rational TimelineLength(); + public slots: /** * @brief Slot for when the track connection is added @@ -102,8 +110,6 @@ protected: virtual QVariant Value(NodeOutput* output, const rational& time) override; private: - rational GetSequenceLength(); - TrackOutput* attached_track(); void AttachTrack(TrackOutput *track); @@ -112,7 +118,9 @@ private: static TrackOutput* TrackFromBlock(Block* block); - NodeInput* track_input_; + NodeInput* video_track_input_; + + NodeInput* audio_track_input_; NodeOutput* length_output_; diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index 1ea68b2a0..f230b947d 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -20,124 +20,93 @@ #include "timeline.h" -#include -#include - TimelinePanel::TimelinePanel(QWidget *parent) : PanelWidget(parent) { // FIXME: This won't work if there's ever more than one of this panel setObjectName("TimelinePanel"); - QWidget* main = new QWidget(this); - setWidget(main); + timeline_widget_ = new TimelineWidget(); + setWidget(timeline_widget_); - QVBoxLayout* layout = new QVBoxLayout(main); - layout->setSpacing(0); - layout->setMargin(0); - - ruler_ = new TimeRuler(true, this); - layout->addWidget(ruler_); - - view_ = new TimelineView(this); - layout->addWidget(view_); - - connect(view_->horizontalScrollBar(), SIGNAL(valueChanged(int)), ruler_, SLOT(SetScroll(int))); - connect(view_, SIGNAL(ScaleChanged(double)), this, SLOT(SetScale(double))); - connect(view_, SIGNAL(TimebaseChanged(const rational&)), this, SLOT(SetTimebase(const rational&))); - connect(ruler_, SIGNAL(TimeChanged(const int64_t&)), view_, SLOT(SetTime(const int64_t&))); - connect(view_, SIGNAL(TimeChanged(const int64_t&)), ruler_, SLOT(SetTime(const int64_t&))); - connect(view_, SIGNAL(TimeChanged(const int64_t&)), this, SIGNAL(TimeChanged(const int64_t&))); - connect(ruler_, SIGNAL(TimeChanged(const int64_t&)), this, SIGNAL(TimeChanged(const int64_t&))); - - // FIXME: Magic number - SetScale(90.0); + connect(timeline_widget_, SIGNAL(TimeChanged(const int64_t&)), this, SIGNAL(TimeChanged(const int64_t&))); Retranslate(); } void TimelinePanel::Clear() { - SetTimebase(0); - - view_->Clear(); + timeline_widget_->Clear(); } void TimelinePanel::SetTimebase(const rational &timebase) { - ruler_->SetTimebase(timebase); - view_->SetTimebase(timebase); + timeline_widget_->SetTimebase(timebase); } void TimelinePanel::SetTime(const int64_t ×tamp) { - ruler_->SetTime(timestamp); - view_->SetTime(timestamp); -} - -TimelineView *TimelinePanel::view() -{ - return view_; + timeline_widget_->SetTime(timestamp); } void TimelinePanel::ConnectTimelineNode(TimelineOutput *node) { - view_->ConnectTimelineNode(node); + timeline_widget_->ConnectTimelineNode(node); } void TimelinePanel::DisconnectTimelineNode() { - view_->DisconnectTimelineNode(); + timeline_widget_->DisconnectTimelineNode(); } void TimelinePanel::ZoomIn() { - SetScale(scale_ * 2); + timeline_widget_->ZoomIn(); } void TimelinePanel::ZoomOut() { - SetScale(scale_ * 0.5); + timeline_widget_->ZoomOut(); } void TimelinePanel::SelectAll() { - view_->SelectAll(); + timeline_widget_->SelectAll(); } void TimelinePanel::DeselectAll() { - view_->DeselectAll(); + timeline_widget_->DeselectAll(); } void TimelinePanel::RippleToIn() { - view_->RippleToIn(); + timeline_widget_->RippleToIn(); } void TimelinePanel::RippleToOut() { - view_->RippleToOut(); + timeline_widget_->RippleToOut(); } void TimelinePanel::EditToIn() { - view_->EditToIn(); + timeline_widget_->EditToIn(); } void TimelinePanel::EditToOut() { - view_->EditToOut(); + timeline_widget_->EditToOut(); } void TimelinePanel::GoToPrevCut() { - view_->GoToPrevCut(); + timeline_widget_->GoToPrevCut(); } void TimelinePanel::GoToNextCut() { - view_->GoToNextCut(); + timeline_widget_->GoToNextCut(); } void TimelinePanel::changeEvent(QEvent *e) @@ -153,11 +122,3 @@ void TimelinePanel::Retranslate() SetTitle(tr("Timeline")); SetSubtitle(tr("(none)")); } - -void TimelinePanel::SetScale(double scale) -{ - scale_ = scale; - - ruler_->SetScale(scale_); - view_->SetScale(scale_); -} diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 2e38ec277..b117c16d9 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -22,9 +22,12 @@ #define TIMELINE_PANEL_H #include "widget/panel/panel.h" -#include "widget/timelineview/timelineview.h" -#include "widget/timeruler/timeruler.h" +#include "widget/timelinewidget/timelinewidget.h" + +/** + * @brief Panel container for a TimelineWidget + */ class TimelinePanel : public PanelWidget { Q_OBJECT @@ -33,8 +36,6 @@ public: void Clear(); - TimelineView* view(); - void ConnectTimelineNode(TimelineOutput* node); void DisconnectTimelineNode(); @@ -73,14 +74,8 @@ signals: private: void Retranslate(); - TimelineView* view_; + TimelineWidget* timeline_widget_; - TimeRuler* ruler_; - - double scale_; - -private slots: - void SetScale(double scale); }; #endif // TIMELINE_PANEL_H diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index 4fc552fbb..597a15b4c 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -27,6 +27,7 @@ add_subdirectory(projecttoolbar) add_subdirectory(slider) add_subdirectory(taskview) add_subdirectory(timelineview) +add_subdirectory(timelinewidget) add_subdirectory(timeruler) add_subdirectory(toolbar) add_subdirectory(viewer) diff --git a/app/widget/timelineview/CMakeLists.txt b/app/widget/timelineview/CMakeLists.txt index 0f1056e3e..80ce02818 100644 --- a/app/widget/timelineview/CMakeLists.txt +++ b/app/widget/timelineview/CMakeLists.txt @@ -27,6 +27,8 @@ set(OLIVE_SOURCES widget/timelineview/timelineviewrect.cpp widget/timelineview/timelineviewblockitem.h widget/timelineview/timelineviewblockitem.cpp + widget/timelineview/timelineviewenditem.h + widget/timelineview/timelineviewenditem.cpp widget/timelineview/timelineviewghostitem.h widget/timelineview/timelineviewghostitem.cpp widget/timelineview/timelineviewplayheaditem.h diff --git a/app/widget/timelineview/timelineview.cpp b/app/widget/timelineview/timelineview.cpp index e66030e3b..ee4916aad 100644 --- a/app/widget/timelineview/timelineview.cpp +++ b/app/widget/timelineview/timelineview.cpp @@ -57,9 +57,12 @@ TimelineView::TimelineView(QWidget *parent) : // Create playhead line playhead_line_ = new TimelineViewPlayheadItem(); - scene_.addItem(playhead_line_); + // Create end item + end_item_ = new TimelineViewEndItem(); + scene_.addItem(end_item_); + // Set default scale SetScale(1.0); } @@ -92,8 +95,6 @@ void TimelineView::AddBlock(Block *block, int track) // Do nothing break; } - - } void TimelineView::RemoveBlock(Block *block) @@ -136,6 +137,7 @@ void TimelineView::SetScale(const double &scale) } playhead_line_->SetScale(scale_); + end_item_->SetScale(scale_); } void TimelineView::SetTimebase(const rational &timebase) @@ -219,89 +221,6 @@ void TimelineView::DeselectAll() } } -void TimelineView::RippleToIn() -{ - RippleEditTo(olive::timeline::kTrimIn, false); -} - -void TimelineView::RippleToOut() -{ - RippleEditTo(olive::timeline::kTrimOut, false); -} - -void TimelineView::EditToIn() -{ - RippleEditTo(olive::timeline::kTrimIn, true); -} - -void TimelineView::EditToOut() -{ - RippleEditTo(olive::timeline::kTrimOut, true); -} - -void TimelineView::GoToPrevCut() -{ - if (timeline_node_ == nullptr) { - return; - } - - if (playhead_ == 0) { - return; - } - - int64_t closest_cut = 0; - - foreach (TrackOutput* track, timeline_node_->Tracks()) { - int64_t this_track_closest_cut = 0; - - foreach (Block* block, track->Blocks()) { - int64_t block_out_ts = olive::time_to_timestamp(block->out(), timebase_); - - if (block_out_ts < playhead_) { - this_track_closest_cut = block_out_ts; - } else { - break; - } - } - - closest_cut = qMax(closest_cut, this_track_closest_cut); - } - - UserSetTime(closest_cut); -} - -void TimelineView::GoToNextCut() -{ - if (timeline_node_ == nullptr) { - return; - } - - int64_t closest_cut = INT64_MAX; - - foreach (TrackOutput* track, timeline_node_->Tracks()) { - int64_t this_track_closest_cut = olive::time_to_timestamp(track->in(), timebase_); - - if (this_track_closest_cut <= playhead_) { - this_track_closest_cut = INT64_MAX; - } - - foreach (Block* block, track->Blocks()) { - int64_t block_in_ts = olive::time_to_timestamp(block->in(), timebase_); - - if (block_in_ts > playhead_) { - this_track_closest_cut = block_in_ts; - break; - } - } - - closest_cut = qMin(closest_cut, this_track_closest_cut); - } - - if (closest_cut < INT64_MAX) { - UserSetTime(closest_cut); - } -} - void TimelineView::SetTime(const int64_t time) { playhead_ = time; @@ -465,65 +384,6 @@ void TimelineView::ClearGhosts() } } -void TimelineView::RippleEditTo(olive::timeline::MovementMode mode, bool insert_gaps) -{ - rational playhead_time = olive::timestamp_to_time(playhead_, timebase_); - - rational closest_point_to_playhead; - if (mode == olive::timeline::kTrimIn) { - closest_point_to_playhead = 0; - } else { - closest_point_to_playhead = RATIONAL_MAX; - } - - foreach (TrackOutput* track, timeline_node_->Tracks()) { - Block* b = track->NearestBlockBefore(playhead_time); - - if (b != nullptr) { - if (mode == olive::timeline::kTrimIn) { - closest_point_to_playhead = qMax(b->in(), closest_point_to_playhead); - } else { - closest_point_to_playhead = qMin(b->out(), closest_point_to_playhead); - } - } - } - - QUndoCommand* command = new QUndoCommand(); - - if (closest_point_to_playhead == playhead_time) { - // Remove one frame only - if (mode == olive::timeline::kTrimIn) { - playhead_time += timebase_; - } else { - playhead_time -= timebase_; - } - } - - rational in_ripple = qMin(closest_point_to_playhead, playhead_time); - rational out_ripple = qMax(closest_point_to_playhead, playhead_time); - rational ripple_length = out_ripple - in_ripple; - - foreach (TrackOutput* track, timeline_node_->Tracks()) { - TrackRippleRemoveAreaCommand* ripple_command = new TrackRippleRemoveAreaCommand(track, - in_ripple, - out_ripple, - command); - - if (insert_gaps) { - GapBlock* gap = new GapBlock(); - gap->set_length(ripple_length); - ripple_command->SetInsert(gap); - } - } - - olive::undo_stack.pushIfHasChildren(command); - - if (mode == olive::timeline::kTrimIn && !insert_gaps) { - int64_t new_time = olive::time_to_timestamp(closest_point_to_playhead, timebase_); - UserSetTime(new_time); - } -} - void TimelineView::UserSetTime(const int64_t &time) { SetTime(time); @@ -553,8 +413,14 @@ void TimelineView::UpdateSceneRect() bounding_rect.setHeight(minimum_height); } + // Ensure the scene is always the full length of the timeline with a gap at the end to work with + if (timeline_node_ != nullptr) { + end_item_->SetEndTime(timeline_node_->TimelineLength()); + end_item_->SetEndPadding(width()/4); + } + // Ensure playhead is the correct height - playhead_line_->UpdateRect(); + playhead_line_->SetHeight(viewport()->height()-1); // If the scene is already this rect, do nothing if (scene_.sceneRect() == bounding_rect) { diff --git a/app/widget/timelineview/timelineview.h b/app/widget/timelineview/timelineview.h index 2b6f07fad..3aa211e8d 100644 --- a/app/widget/timelineview/timelineview.h +++ b/app/widget/timelineview/timelineview.h @@ -30,6 +30,7 @@ #include "node/block/clip/clip.h" #include "node/output/timeline/timeline.h" #include "timelineviewblockitem.h" +#include "timelineviewenditem.h" #include "timelineviewghostitem.h" #include "timelineviewplayheaditem.h" #include "widget/timelineview/undo/undo.h" @@ -44,7 +45,7 @@ class TimelineView : public QGraphicsView { Q_OBJECT public: - TimelineView(QWidget* parent); + TimelineView(QWidget* parent = nullptr); void SetScale(const double& scale); @@ -56,18 +57,6 @@ public: void DeselectAll(); - void RippleToIn(); - - void RippleToOut(); - - void EditToIn(); - - void EditToOut(); - - void GoToPrevCut(); - - void GoToNextCut(); - public slots: void SetTimebase(const rational& timebase); @@ -360,8 +349,6 @@ private: void ClearGhosts(); - void RippleEditTo(olive::timeline::MovementMode mode, bool insert_gaps); - void UserSetTime(const int64_t& time); QGraphicsScene scene_; @@ -382,6 +369,8 @@ private: TimelineViewPlayheadItem* playhead_line_; + TimelineViewEndItem* end_item_; + Tool* active_tool_; private slots: diff --git a/app/widget/timelineview/timelineviewblockitem.cpp b/app/widget/timelineview/timelineviewblockitem.cpp index d4864cb2e..8d0c326f1 100644 --- a/app/widget/timelineview/timelineviewblockitem.cpp +++ b/app/widget/timelineview/timelineviewblockitem.cpp @@ -74,13 +74,13 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI return; } - QLinearGradient grad; + /*QLinearGradient grad; grad.setStart(0, rect().top()); grad.setFinalStop(0, rect().bottom()); grad.setColorAt(0.0, QColor(160, 160, 240)); grad.setColorAt(1.0, QColor(128, 128, 192)); - painter->fillRect(rect(), grad); -// painter->fillRect(rect(), QColor(128, 128, 192)); + painter->fillRect(rect(), grad);*/ + painter->fillRect(rect(), QColor(128, 128, 192)); if (option->state & QStyle::State_Selected) { painter->fillRect(rect(), QColor(0, 0, 0, 64)); diff --git a/app/widget/timelineview/timelineviewenditem.cpp b/app/widget/timelineview/timelineviewenditem.cpp new file mode 100644 index 000000000..4956e8686 --- /dev/null +++ b/app/widget/timelineview/timelineviewenditem.cpp @@ -0,0 +1,37 @@ +#include "timelineviewenditem.h" + +TimelineViewEndItem::TimelineViewEndItem(QGraphicsItem *parent) : + TimelineViewRect(parent), + end_padding_(0) +{ +} + +void TimelineViewEndItem::SetEndTime(const rational &time) +{ + end_time_ = time; + + UpdateRect(); +} + +void TimelineViewEndItem::SetEndPadding(int padding) +{ + end_padding_ = padding; + + UpdateRect(); +} + +void TimelineViewEndItem::UpdateRect() +{ + // Doesn't need to be more than one pixel + setRect(0, 0, 1, 1); + + setPos(TimeToScreenCoord(end_time_) + end_padding_, 0); +} + +void TimelineViewEndItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + // Item is invisible, this is a no-op + Q_UNUSED(painter) + Q_UNUSED(option) + Q_UNUSED(widget) +} diff --git a/app/widget/timelineview/timelineviewenditem.h b/app/widget/timelineview/timelineviewenditem.h new file mode 100644 index 000000000..4161cd6a3 --- /dev/null +++ b/app/widget/timelineview/timelineviewenditem.h @@ -0,0 +1,29 @@ +#ifndef TIMELINEVIEWENDITEM_H +#define TIMELINEVIEWENDITEM_H + +#include "timelineviewrect.h" + +/** + * @brief An item placed at the end point of the Timeline to ensure the correct scene size + */ +class TimelineViewEndItem : public TimelineViewRect +{ +public: + TimelineViewEndItem(QGraphicsItem* parent = nullptr); + + void SetEndTime(const rational& time); + + void SetEndPadding(int padding); + + virtual void UpdateRect() override; + +protected: + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; + +private: + rational end_time_; + + int end_padding_; +}; + +#endif // TIMELINEVIEWENDITEM_H diff --git a/app/widget/timelineview/timelineviewplayheaditem.cpp b/app/widget/timelineview/timelineviewplayheaditem.cpp index d48774d0b..f2be86860 100644 --- a/app/widget/timelineview/timelineviewplayheaditem.cpp +++ b/app/widget/timelineview/timelineviewplayheaditem.cpp @@ -52,7 +52,7 @@ void TimelineViewPlayheadItem::UpdateRect() double x = TimeToScreenCoord(rational(playhead_ * timebase_.numerator(), timebase_.denominator())); double width = TimeToScreenCoord(timebase_); - setRect(0, 0, width, scene()->height()-1); + setRect(0, 0, width, height_); setPos(x, 0); } diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp new file mode 100644 index 000000000..818a47852 --- /dev/null +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -0,0 +1,316 @@ +#include "timelinewidget.h" + +#include +#include +#include + +#include "common/timecodefunctions.h" + +TimelineWidget::TimelineWidget(QWidget *parent) : + QWidget(parent), + timeline_node_(nullptr), + playhead_(0) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setSpacing(0); + layout->setMargin(0); + + ruler_ = new TimeRuler(true); + connect(ruler_, SIGNAL(TimeChanged(const int64_t&)), this, SIGNAL(TimeChanged(const int64_t&))); + connect(ruler_, SIGNAL(TimeChanged(const int64_t&)), this, SLOT(UpdateInternalTime(const int64_t&))); + layout->addWidget(ruler_); + + // Create list of TimelineViews - these MUST correspond to the ViewType enum + + QSplitter* view_splitter = new QSplitter(Qt::Vertical); + view_splitter->setChildrenCollapsible(false); + layout->addWidget(view_splitter); + + // Video view + views_.append(new TimelineView()); + + // Audio view + views_.append(new TimelineView()); + + // Global scrollbar + horizontal_scroll_ = new QScrollBar(Qt::Horizontal); + connect(horizontal_scroll_, SIGNAL(valueChanged(int)), ruler_, SLOT(SetScroll(int))); + connect(views_.first()->horizontalScrollBar(), SIGNAL(rangeChanged(int, int)), horizontal_scroll_, SLOT(setRange(int, int))); + layout->addWidget(horizontal_scroll_); + + foreach (TimelineView* view, views_) { + view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + view_splitter->addWidget(view); + + connect(view->horizontalScrollBar(), SIGNAL(valueChanged(int)), ruler_, SLOT(SetScroll(int))); + connect(view, SIGNAL(ScaleChanged(double)), this, SLOT(SetScale(double))); + connect(view, SIGNAL(TimebaseChanged(const rational&)), this, SLOT(SetTimebase(const rational&))); + connect(ruler_, SIGNAL(TimeChanged(const int64_t&)), view, SLOT(SetTime(const int64_t&))); + connect(view, SIGNAL(TimeChanged(const int64_t&)), ruler_, SLOT(SetTime(const int64_t&))); + connect(view, SIGNAL(TimeChanged(const int64_t&)), this, SIGNAL(TimeChanged(const int64_t&))); + connect(horizontal_scroll_, SIGNAL(valueChanged(int)), view->horizontalScrollBar(), SLOT(setValue(int))); + connect(view->horizontalScrollBar(), SIGNAL(valueChanged(int)), horizontal_scroll_, SLOT(setValue(int))); + + // Connect each view's scroll to each other + foreach (TimelineView* other_view, views_) { + if (view != other_view) { + connect(view->horizontalScrollBar(), SIGNAL(valueChanged(int)), other_view->horizontalScrollBar(), SLOT(setValue(int))); + } + } + } + + // FIXME: Magic number + SetScale(90.0); +} + +void TimelineWidget::Clear() +{ + SetTimebase(0); + + foreach (TimelineView* view, views_) { + view->Clear(); + } +} + +void TimelineWidget::SetTimebase(const rational &timebase) +{ + timebase_ = timebase; + + ruler_->SetTimebase(timebase); + + foreach (TimelineView* view, views_) { + view->SetTimebase(timebase); + } +} + +void TimelineWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + + horizontal_scroll_->setPageStep(horizontal_scroll_->width()); +} + +void TimelineWidget::SetTime(const int64_t ×tamp) +{ + ruler_->SetTime(timestamp); + + foreach (TimelineView* view, views_) { + view->SetTime(timestamp); + } + + UpdateInternalTime(timestamp); +} + +void TimelineWidget::ConnectTimelineNode(TimelineOutput *node) +{ + timeline_node_ = node; + + foreach (TimelineView* view, views_) { + view->ConnectTimelineNode(node); + } +} + +void TimelineWidget::DisconnectTimelineNode() +{ + timeline_node_ = nullptr; + + foreach (TimelineView* view, views_) { + view->DisconnectTimelineNode(); + } +} + +void TimelineWidget::ZoomIn() +{ + SetScale(scale_ * 2); +} + +void TimelineWidget::ZoomOut() +{ + SetScale(scale_ * 0.5); +} + +void TimelineWidget::SelectAll() +{ + foreach (TimelineView* view, views_) { + view->SelectAll(); + } +} + +void TimelineWidget::DeselectAll() +{ + foreach (TimelineView* view, views_) { + view->DeselectAll(); + } +} + +TimelineView *TimelineWidget::GetView(const TimelineWidget::ViewType &view_type) +{ + return views_.at(view_type); +} + +void TimelineWidget::RippleToIn() +{ + RippleEditTo(olive::timeline::kTrimIn, false); +} + +void TimelineWidget::RippleToOut() +{ + RippleEditTo(olive::timeline::kTrimOut, false); +} + +void TimelineWidget::EditToIn() +{ + RippleEditTo(olive::timeline::kTrimIn, true); +} + +void TimelineWidget::EditToOut() +{ + RippleEditTo(olive::timeline::kTrimOut, true); +} + +void TimelineWidget::GoToPrevCut() +{ + if (timeline_node_ == nullptr) { + return; + } + + if (playhead_ == 0) { + return; + } + + int64_t closest_cut = 0; + + foreach (TrackOutput* track, timeline_node_->Tracks()) { + int64_t this_track_closest_cut = 0; + + foreach (Block* block, track->Blocks()) { + int64_t block_out_ts = olive::time_to_timestamp(block->out(), timebase_); + + if (block_out_ts < playhead_) { + this_track_closest_cut = block_out_ts; + } else { + break; + } + } + + closest_cut = qMax(closest_cut, this_track_closest_cut); + } + + SetTimeAndSignal(closest_cut); +} + +void TimelineWidget::GoToNextCut() +{ + if (timeline_node_ == nullptr) { + return; + } + + int64_t closest_cut = INT64_MAX; + + foreach (TrackOutput* track, timeline_node_->Tracks()) { + int64_t this_track_closest_cut = olive::time_to_timestamp(track->in(), timebase_); + + if (this_track_closest_cut <= playhead_) { + this_track_closest_cut = INT64_MAX; + } + + foreach (Block* block, track->Blocks()) { + int64_t block_in_ts = olive::time_to_timestamp(block->in(), timebase_); + + if (block_in_ts > playhead_) { + this_track_closest_cut = block_in_ts; + break; + } + } + + closest_cut = qMin(closest_cut, this_track_closest_cut); + } + + if (closest_cut < INT64_MAX) { + SetTimeAndSignal(closest_cut); + } +} + +void TimelineWidget::RippleEditTo(olive::timeline::MovementMode mode, bool insert_gaps) +{ + rational playhead_time = olive::timestamp_to_time(playhead_, timebase_); + + rational closest_point_to_playhead; + if (mode == olive::timeline::kTrimIn) { + closest_point_to_playhead = 0; + } else { + closest_point_to_playhead = RATIONAL_MAX; + } + + foreach (TrackOutput* track, timeline_node_->Tracks()) { + Block* b = track->NearestBlockBefore(playhead_time); + + if (b != nullptr) { + if (mode == olive::timeline::kTrimIn) { + closest_point_to_playhead = qMax(b->in(), closest_point_to_playhead); + } else { + closest_point_to_playhead = qMin(b->out(), closest_point_to_playhead); + } + } + } + + QUndoCommand* command = new QUndoCommand(); + + if (closest_point_to_playhead == playhead_time) { + // Remove one frame only + if (mode == olive::timeline::kTrimIn) { + playhead_time += timebase_; + } else { + playhead_time -= timebase_; + } + } + + rational in_ripple = qMin(closest_point_to_playhead, playhead_time); + rational out_ripple = qMax(closest_point_to_playhead, playhead_time); + rational ripple_length = out_ripple - in_ripple; + + foreach (TrackOutput* track, timeline_node_->Tracks()) { + TrackRippleRemoveAreaCommand* ripple_command = new TrackRippleRemoveAreaCommand(track, + in_ripple, + out_ripple, + command); + + if (insert_gaps) { + GapBlock* gap = new GapBlock(); + gap->set_length(ripple_length); + ripple_command->SetInsert(gap); + } + } + + olive::undo_stack.pushIfHasChildren(command); + + if (mode == olive::timeline::kTrimIn && !insert_gaps) { + int64_t new_time = olive::time_to_timestamp(closest_point_to_playhead, timebase_); + + SetTimeAndSignal(new_time); + } +} + +void TimelineWidget::SetTimeAndSignal(const int64_t &t) +{ + SetTime(t); + emit TimeChanged(t); +} + +void TimelineWidget::SetScale(double scale) +{ + scale_ = scale; + + ruler_->SetScale(scale_); + + foreach (TimelineView* view, views_) { + view->SetScale(scale_); + } +} + +void TimelineWidget::UpdateInternalTime(const int64_t ×tamp) +{ + playhead_ = timestamp; +} diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h new file mode 100644 index 000000000..82fcae24e --- /dev/null +++ b/app/widget/timelinewidget/timelinewidget.h @@ -0,0 +1,91 @@ +#ifndef TIMELINEWIDGET_H +#define TIMELINEWIDGET_H + +#include +#include + +#include "widget/timelineview/timelineview.h" +#include "widget/timeruler/timeruler.h" + +/** + * @brief Full widget for working with TimelineOutput nodes + * + * Encapsulates TimelineViews, TimeRulers, and scrollbars for a complete widget to manipulate Timelines + */ +class TimelineWidget : public QWidget +{ + Q_OBJECT +public: + TimelineWidget(QWidget* parent = nullptr); + + void Clear(); + + void SetTime(const int64_t& timestamp); + + void ConnectTimelineNode(TimelineOutput* node); + + void DisconnectTimelineNode(); + + void ZoomIn(); + + void ZoomOut(); + + void SelectAll(); + + void DeselectAll(); + + void RippleToIn(); + + void RippleToOut(); + + void EditToIn(); + + void EditToOut(); + + void GoToPrevCut(); + + void GoToNextCut(); + +public slots: + void SetTimebase(const rational& timebase); + +protected: + virtual void resizeEvent(QResizeEvent *event) override; + +signals: + void TimeChanged(const int64_t& time); + +private: + enum ViewType { + kVideo, + kAudio + }; + + TimelineView* GetView(const ViewType& view_type); + + void RippleEditTo(olive::timeline::MovementMode mode, bool insert_gaps); + + void SetTimeAndSignal(const int64_t& t); + + QVector views_; + + TimeRuler* ruler_; + + double scale_; + + TimelineOutput* timeline_node_; + + rational timebase_; + + int64_t playhead_; + + QScrollBar* horizontal_scroll_; + +private slots: + void SetScale(double scale); + + void UpdateInternalTime(const int64_t& timestamp); + +}; + +#endif // TIMELINEWIDGET_H