From cf3e8d80d1964303b70cf1e6998500db775a270d Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 24 Sep 2019 22:45:25 +1000 Subject: [PATCH] implemented 'ripple to' timeline actions --- app/node/block/block.cpp | 7 ++ app/node/block/block.h | 2 + app/node/block/clip/clip.cpp | 3 +- app/node/block/gap/gap.cpp | 3 +- app/node/output/track/track.cpp | 4 +- app/panel/timeline/timeline.cpp | 21 ++++++ app/panel/timeline/timeline.h | 8 ++ app/widget/panel/panel.cpp | 36 --------- app/widget/panel/panel.h | 26 ++++--- app/widget/timelineview/timelineview.cpp | 93 ++++++++++++++++++++++-- app/widget/timelineview/timelineview.h | 12 +++ app/widget/timelineview/undo/undo.cpp | 33 +++++---- app/widget/timelineview/undo/undo.h | 1 + app/widget/timeruler/timeruler.cpp | 11 ++- app/widget/timeruler/timeruler.h | 4 + app/window/mainwindow/mainmenu.cpp | 32 ++++++-- app/window/mainwindow/mainmenu.h | 5 ++ 17 files changed, 219 insertions(+), 82 deletions(-) diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 018e51407..6edf4a394 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -221,3 +221,10 @@ rational Block::MediaToSequenceTime(const rational &media_time) return media_time - media_in() + in(); } + +void Block::CopyParameters(Block *source, Block *dest) +{ + dest->set_block_name(source->block_name()); + dest->set_length(source->length()); + dest->set_media_in(source->media_in()); +} diff --git a/app/node/block/block.h b/app/node/block/block.h index 61e86cef4..02ee4c519 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -102,6 +102,8 @@ protected: rational MediaToSequenceTime(const rational& media_time); + static void CopyParameters(Block* source, Block* dest); + private: NodeInput* previous_input_; NodeOutput* block_output_; diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 136336f5d..37b802c6f 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -33,8 +33,7 @@ Block *ClipBlock::copy() { ClipBlock* c = new ClipBlock(); - c->set_block_name(block_name()); - c->set_length(length()); + CopyParameters(this, c); return c; } diff --git a/app/node/block/gap/gap.cpp b/app/node/block/gap/gap.cpp index af7a2161e..a027361dc 100644 --- a/app/node/block/gap/gap.cpp +++ b/app/node/block/gap/gap.cpp @@ -28,8 +28,7 @@ Block *GapBlock::copy() { GapBlock* c = new GapBlock(); - c->set_block_name(block_name()); - c->set_length(length()); + CopyParameters(this, c); return c; } diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 017e39d0d..07bd12c1e 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -151,7 +151,7 @@ Block *TrackOutput::NearestBlockBefore(const rational &time) } } - return nullptr; + return this; } Block *TrackOutput::NearestBlockAfter(const rational &time) @@ -163,7 +163,7 @@ Block *TrackOutput::NearestBlockAfter(const rational &time) } } - return nullptr; + return this; } const QVector &TrackOutput::Blocks() diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index 3b348f723..a05beb2f0 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -43,6 +43,7 @@ TimelinePanel::TimelinePanel(QWidget *parent) : 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&))); // FIXME: Magic number SetScale(90.0); @@ -98,6 +99,26 @@ void TimelinePanel::DeselectAll() view_->DeselectAll(); } +void TimelinePanel::RippleToIn() +{ + view_->RippleToIn(); +} + +void TimelinePanel::RippleToOut() +{ + view_->RippleToOut(); +} + +void TimelinePanel::EditToIn() +{ + view_->EditToIn(); +} + +void TimelinePanel::EditToOut() +{ + view_->EditToOut(); +} + void TimelinePanel::changeEvent(QEvent *e) { if (e->type() == QEvent::LanguageChange) { diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index a2a0bb1a4..587c218af 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -47,6 +47,14 @@ public: virtual void DeselectAll() override; + virtual void RippleToIn() override; + + virtual void RippleToOut() override; + + virtual void EditToIn() override; + + virtual void EditToOut() override; + public slots: void SetTimebase(const rational& timebase); diff --git a/app/widget/panel/panel.cpp b/app/widget/panel/panel.cpp index 1303da216..1ede85d1e 100644 --- a/app/widget/panel/panel.cpp +++ b/app/widget/panel/panel.cpp @@ -56,42 +56,6 @@ void PanelWidget::SetBorderVisible(bool enabled) update(); } -void PanelWidget::ZoomIn() -{ -} - -void PanelWidget::ZoomOut() -{ -} - -void PanelWidget::GoToStart() -{ -} - -void PanelWidget::PrevFrame() -{ -} - -void PanelWidget::PlayPause() -{ -} - -void PanelWidget::NextFrame() -{ -} - -void PanelWidget::GoToEnd() -{ -} - -void PanelWidget::SelectAll() -{ -} - -void PanelWidget::DeselectAll() -{ -} - void PanelWidget::SetTitle(const QString &t) { title_ = t; diff --git a/app/widget/panel/panel.h b/app/widget/panel/panel.h index a5971fe8c..a90e8e0b3 100644 --- a/app/widget/panel/panel.h +++ b/app/widget/panel/panel.h @@ -58,7 +58,7 @@ public: * This function is up to the Panel's interpretation of what the user intends to zoom into. Default behavior is a * no-op. */ - virtual void ZoomIn(); + virtual void ZoomIn(){} /** * @brief Called whenever this panel is focused and user uses "Zoom Out" (either in menus or as a keyboard shortcut) @@ -66,11 +66,11 @@ public: * This function is up to the Panel's interpretation of what the user intends to zoom out of. Default behavior is a * no-op. */ - virtual void ZoomOut(); + virtual void ZoomOut(){} - virtual void GoToStart(); + virtual void GoToStart(){} - virtual void PrevFrame(); + virtual void PrevFrame(){} /** * @brief Called whenever this panel is focused and user uses "Play/Pause" (either in menus or as a keyboard shortcut) @@ -78,15 +78,23 @@ public: * This function is up to the Panel's interpretation of what the user intends to zoom out of. Default behavior is a * no-op. */ - virtual void PlayPause(); + virtual void PlayPause(){} - virtual void NextFrame(); + virtual void NextFrame(){} - virtual void GoToEnd(); + virtual void GoToEnd(){} - virtual void SelectAll(); + virtual void SelectAll(){} - virtual void DeselectAll(); + virtual void DeselectAll(){} + + virtual void RippleToIn(){} + + virtual void RippleToOut(){} + + virtual void EditToIn(){} + + virtual void EditToOut(){} protected: /** diff --git a/app/widget/timelineview/timelineview.cpp b/app/widget/timelineview/timelineview.cpp index bb8a96d9e..cc5706dd1 100644 --- a/app/widget/timelineview/timelineview.cpp +++ b/app/widget/timelineview/timelineview.cpp @@ -27,6 +27,7 @@ #include #include +#include "common/timecodefunctions.h" #include "core.h" #include "node/input/media/media.h" #include "project/item/footage/footage.h" @@ -217,6 +218,26 @@ 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::SetTime(const int64_t time) { playhead_ = time; @@ -228,43 +249,51 @@ void TimelineView::mousePressEvent(QMouseEvent *event) { active_tool_ = GetActiveTool(); - if (active_tool_ != nullptr) { + if (timeline_node_ != nullptr && active_tool_ != nullptr) { active_tool_->MousePress(event); } } void TimelineView::mouseMoveEvent(QMouseEvent *event) { - if (active_tool_ != nullptr) { + if (timeline_node_ != nullptr && active_tool_ != nullptr) { active_tool_->MouseMove(event); } } void TimelineView::mouseReleaseEvent(QMouseEvent *event) { - if (active_tool_ != nullptr) { + if (timeline_node_ != nullptr && active_tool_ != nullptr) { active_tool_->MouseRelease(event); } } void TimelineView::dragEnterEvent(QDragEnterEvent *event) { - import_tool_.DragEnter(event); + if (timeline_node_ != nullptr) { + import_tool_.DragEnter(event); + } } void TimelineView::dragMoveEvent(QDragMoveEvent *event) { - import_tool_.DragMove(event); + if (timeline_node_ != nullptr) { + import_tool_.DragMove(event); + } } void TimelineView::dragLeaveEvent(QDragLeaveEvent *event) { - import_tool_.DragLeave(event); + if (timeline_node_ != nullptr) { + import_tool_.DragLeave(event); + } } void TimelineView::dropEvent(QDropEvent *event) { - import_tool_.DragDrop(event); + if (timeline_node_ != nullptr) { + import_tool_.DragDrop(event); + } } void TimelineView::resizeEvent(QResizeEvent *event) @@ -372,6 +401,56 @@ 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_; + } + } + + foreach (TrackOutput* track, timeline_node_->Tracks()) { + new TrackRippleRemoveAreaCommand(track, + qMin(closest_point_to_playhead, playhead_time), + qMax(closest_point_to_playhead, playhead_time), + command); + } + + 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_); + SetTime(new_time); + emit TimeChanged(new_time); + } +} + void TimelineView::BlockChanged() { TimelineViewRect* rect = block_items_[static_cast(sender())]; diff --git a/app/widget/timelineview/timelineview.h b/app/widget/timelineview/timelineview.h index 08190f47d..975a91320 100644 --- a/app/widget/timelineview/timelineview.h +++ b/app/widget/timelineview/timelineview.h @@ -56,6 +56,14 @@ public: void DeselectAll(); + void RippleToIn(); + + void RippleToOut(); + + void EditToIn(); + + void EditToOut(); + public slots: void SetTimebase(const rational& timebase); @@ -76,6 +84,8 @@ signals: void TimebaseChanged(const rational& timebase); + void TimeChanged(const int64_t& time); + protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; @@ -346,6 +356,8 @@ private: void ClearGhosts(); + void RippleEditTo(olive::timeline::MovementMode mode, bool insert_gaps); + QGraphicsScene scene_; double scale_; diff --git a/app/widget/timelineview/undo/undo.cpp b/app/widget/timelineview/undo/undo.cpp index eb2adbac3..fa9f82aa3 100644 --- a/app/widget/timelineview/undo/undo.cpp +++ b/app/widget/timelineview/undo/undo.cpp @@ -29,7 +29,6 @@ Block* CreateSplitBlock(Block* block, rational point, QObject* parent = nullptr) copy->set_length_and_media_in(block->length() - (point - block->in())); copy->setParent(parent); - return copy; } @@ -142,7 +141,8 @@ TrackRippleRemoveAreaCommand::TrackRippleRemoveAreaCommand(TrackOutput *track, r out_(out), splice_(nullptr), trim_out_(nullptr), - trim_in_(nullptr) + trim_in_(nullptr), + insert_(nullptr) { } @@ -168,40 +168,39 @@ void TrackRippleRemoveAreaCommand::redo() } } + track_->BlockInvalidateCache(); + // If we picked up a block to splice if (splice_ != nullptr) { // Split the block here - Block* copy = CreateSplitBlock(splice_, in_); + Block* copy = CreateSplitBlock(splice_, out_); + + splice_original_length_ = splice_->length(); + splice_->set_length(out_ - splice_->in()); + + track_->AddBlockToGraph(copy); + Node::CopyInputs(splice_, copy); + + track_->InsertBlockAfter(copy, splice_); // Perform all further actions as if we were just trimming these clips trim_out_ = splice_; trim_in_ = copy; - } // If we picked up a block to trim the in point of - if (trim_in_ != nullptr && trim_in_->in() < out_) { + if (trim_in_ != nullptr) { trim_in_old_length_ = trim_in_->length(); trim_in_new_length_ = trim_in_->out() - out_; } // If we picked up a block to trim the out point of - if (trim_out_ != nullptr && trim_out_->out() > in_) { + if (trim_out_ != nullptr) { trim_out_old_length_ = trim_out_->length(); trim_out_new_length_ = in_ - trim_out_->in(); } - track_->BlockInvalidateCache(); - - // If we're splicing, trim_in_ is a copy - if (splice_ != nullptr) { - track_->AddBlockToGraph(trim_in_); - Node::CopyInputs(splice_, trim_in_); - - track_->InsertBlockAfter(trim_in_, splice_); - } - // If we picked up a block to trim the in point of if (trim_in_old_length_ != trim_in_new_length_) { trim_in_->set_length_and_media_in(trim_in_new_length_); @@ -272,6 +271,8 @@ void TrackRippleRemoveAreaCommand::undo() // Remove node TakeNodeFromParentGraph(trim_in_, &memory_manager_); + + splice_->set_length(splice_original_length_); } track_->UnblockInvalidateCache(); diff --git a/app/widget/timelineview/undo/undo.h b/app/widget/timelineview/undo/undo.h index 6c7206ea1..05a5ecc6d 100644 --- a/app/widget/timelineview/undo/undo.h +++ b/app/widget/timelineview/undo/undo.h @@ -131,6 +131,7 @@ protected: rational out_; Block* splice_; + rational splice_original_length_; Block* trim_out_; QVector removed_blocks_; diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index 8068bd1ab..9cde28392 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -28,13 +28,15 @@ #include "common/timecodefunctions.h" #include "common/qtversionabstraction.h" #include "config/config.h" +#include "core.h" TimeRuler::TimeRuler(bool text_visible, QWidget* parent) : QWidget(parent), scroll_(0), centered_text_(true), - scale_(1.0), // FIXME: Temporary value - time_(0) + scale_(1.0), + time_(0), + snapping_(false) { QFontMetrics fm = fontMetrics(); @@ -92,6 +94,11 @@ void TimeRuler::SetTimebase(const rational &r) update(); } +void TimeRuler::SetSnapping(bool snapping) +{ + snapping_ = snapping; +} + const int64_t &TimeRuler::GetTime() { return time_; diff --git a/app/widget/timeruler/timeruler.h b/app/widget/timeruler/timeruler.h index 0a1c841db..ebe0f9752 100644 --- a/app/widget/timeruler/timeruler.h +++ b/app/widget/timeruler/timeruler.h @@ -42,6 +42,8 @@ public: void SetCenteredText(bool c); + void SetSnapping(bool snapping); + const int64_t& GetTime(); public slots: @@ -94,6 +96,8 @@ private: TimelinePlayhead style_; + bool snapping_; + }; #endif // TIMERULER_H diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index 98a2f7473..4b6f55288 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -55,10 +55,10 @@ MainMenu::MainMenu(QMainWindow *parent) : // edit_menu_ = new Menu(this); - edit_undo_item_ = olive::undo_stack.createUndoAction(this); //edit_menu_->AddItem("undo", nullptr, nullptr, "Ctrl+Z"); + edit_undo_item_ = olive::undo_stack.createUndoAction(this); Menu::ConformItem(edit_undo_item_, "undo", nullptr, nullptr, "Ctrl+Z"); edit_menu_->addAction(edit_undo_item_); - edit_redo_item_ = olive::undo_stack.createRedoAction(this); //edit_menu_->AddItem("redo", nullptr, nullptr, "Ctrl+Shift+Z"); + edit_redo_item_ = olive::undo_stack.createRedoAction(this); Menu::ConformItem(edit_redo_item_, "redo", nullptr, nullptr, "Ctrl+Shift+Z"); edit_menu_->addAction(edit_redo_item_); @@ -70,10 +70,10 @@ MainMenu::MainMenu(QMainWindow *parent) : edit_menu_->addSeparator(); olive::menu_shared.AddItemsForClipEditMenu(edit_menu_); edit_menu_->addSeparator(); - edit_ripple_to_in_item_ = edit_menu_->AddItem("rippletoin", nullptr, nullptr, "Q"); - edit_ripple_to_out_item_ = edit_menu_->AddItem("rippletoout", nullptr, nullptr, "W"); - edit_edit_to_in_item_ = edit_menu_->AddItem("edittoin", nullptr, nullptr, "Ctrl+Alt+Q"); - edit_edit_to_out_item_ = edit_menu_->AddItem("edittoout", nullptr, nullptr, "Ctrl+Alt+W"); + edit_ripple_to_in_item_ = edit_menu_->AddItem("rippletoin", this, SLOT(RippleToInTriggered()), "Q"); + edit_ripple_to_out_item_ = edit_menu_->AddItem("rippletoout", this, SLOT(RippleToOutTriggered()), "W"); + edit_edit_to_in_item_ = edit_menu_->AddItem("edittoin", this, SLOT(EditToInTriggered()), "Ctrl+Alt+Q"); + edit_edit_to_out_item_ = edit_menu_->AddItem("edittoout", this, SLOT(EditToOutTriggered()), "Ctrl+Alt+W"); edit_menu_->addSeparator(); olive::menu_shared.AddItemsForInOutMenu(edit_menu_); edit_delete_inout_item_ = edit_menu_->AddItem("deleteinout", nullptr, nullptr, ";"); @@ -407,6 +407,26 @@ void MainMenu::DeselectAllTriggered() olive::panel_manager->CurrentlyFocused()->DeselectAll(); } +void MainMenu::RippleToInTriggered() +{ + olive::panel_manager->CurrentlyFocused()->RippleToIn(); +} + +void MainMenu::RippleToOutTriggered() +{ + olive::panel_manager->CurrentlyFocused()->RippleToOut(); +} + +void MainMenu::EditToInTriggered() +{ + olive::panel_manager->CurrentlyFocused()->EditToIn(); +} + +void MainMenu::EditToOutTriggered() +{ + olive::panel_manager->CurrentlyFocused()->EditToOut(); +} + void MainMenu::Retranslate() { // MenuShared is not a QWidget and therefore does not receive a LanguageEvent, we use MainMenu's to update it diff --git a/app/window/mainwindow/mainmenu.h b/app/window/mainwindow/mainmenu.h index 3a567fa8b..e343abc57 100644 --- a/app/window/mainwindow/mainmenu.h +++ b/app/window/mainwindow/mainmenu.h @@ -101,6 +101,11 @@ private slots: void SelectAllTriggered(); void DeselectAllTriggered(); + void RippleToInTriggered(); + void RippleToOutTriggered(); + void EditToInTriggered(); + void EditToOutTriggered(); + private: /** * @brief Set strings based on the current application language.