From 62e94ca2cb7d400ba6d3087af6c5b7b9eadce2f7 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 2 Jan 2020 01:30:43 +1100 Subject: [PATCH] improved a number of pointer tool functions and fixed a number of bugs --- app/node/output/timeline/tracklist.cpp | 2 + app/widget/nodeview/nodeviewundo.cpp | 21 +++++ app/widget/nodeview/nodeviewundo.h | 22 +++++ app/widget/timelinewidget/timelinewidget.cpp | 60 ++---------- app/widget/timelinewidget/tool/pointer.cpp | 45 ++++++++- app/widget/timelinewidget/undo/undo.cpp | 92 ++++++++++++++++++- app/widget/timelinewidget/undo/undo.h | 26 ++++++ .../timelinewidget/view/timelineview.cpp | 4 + 8 files changed, 216 insertions(+), 56 deletions(-) diff --git a/app/node/output/timeline/tracklist.cpp b/app/node/output/timeline/tracklist.cpp index f944332a8..a9aa39c61 100644 --- a/app/node/output/timeline/tracklist.cpp +++ b/app/node/output/timeline/tracklist.cpp @@ -143,6 +143,8 @@ void TrackList::RemoveTrack() GetParentGraph()->TakeNode(track); delete track; + + track_input_->RemoveLast(); } void TrackList::TrackConnected(NodeEdgePtr edge) diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index 4c8cd693b..bf6c15187 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -134,3 +134,24 @@ NodeRemoveWithExclusiveDeps::NodeRemoveWithExclusiveDeps(NodeGraph *graph, Node remove_command_ = new NodeRemoveCommand(graph, node_and_its_deps, this); } + +NodeCopyInputsCommand::NodeCopyInputsCommand(Node *src, Node *dest, bool include_connections, QUndoCommand *parent) : + QUndoCommand(parent), + src_(src), + dest_(dest), + include_connections_(include_connections) +{ +} + +NodeCopyInputsCommand::NodeCopyInputsCommand(Node *src, Node *dest, QUndoCommand *parent) : + QUndoCommand(parent), + src_(src), + dest_(dest), + include_connections_(true) +{ +} + +void NodeCopyInputsCommand::redo() +{ + Node::CopyInputs(src_, dest_, include_connections_); +} diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 14ae422ad..c6f8ba826 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -88,4 +88,26 @@ private: NodeRemoveCommand* remove_command_; }; +class NodeCopyInputsCommand : public QUndoCommand { +public: + NodeCopyInputsCommand(Node* src, + Node* dest, + bool include_connections = true, + QUndoCommand* parent = nullptr); + + NodeCopyInputsCommand(Node* src, + Node* dest, + QUndoCommand* parent = nullptr); + + virtual void redo() override; + +private: + Node* src_; + + Node* dest_; + + bool include_connections_; + +}; + #endif // NODEVIEWUNDO_H diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 336462b96..32e024a28 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -389,60 +389,20 @@ void TimelineWidget::SplitAtPlayhead() void TimelineWidget::DeleteSelectedInternal(const QList blocks, bool remove_from_graph, QUndoCommand *command) { foreach (Block* b, blocks) { - // All this function does is replace blocks with gaps, nothing to do if the block is already a gap - if (b->type() == Block::kGap) { - continue; - } - - bool previous_is_gap = (b->previous() && b->previous()->type() == Block::kGap); - bool next_is_gap = (b->next() && b->next()->type() == Block::kGap); - TrackOutput* original_track = TrackOutput::TrackFromBlock(b); - if (!b->next()) { - // If the block has no next, presumably it's the last block and doesn't actually need a gap - new TrackRippleRemoveBlockCommand(original_track, - b, - command); - } else if (!previous_is_gap && !next_is_gap) { - // Make new gap and replace old Block with it for now - GapBlock* gap = new GapBlock(); - gap->set_length_and_media_out(b->length()); + // Make new gap and replace old Block with it for now + GapBlock* gap = new GapBlock(); + gap->set_length_and_media_out(b->length()); - new NodeAddCommand(static_cast(b->parent()), - gap, - command); + new NodeAddCommand(static_cast(b->parent()), + gap, + command); - new TrackReplaceBlockCommand(original_track, - b, - gap, - command); - } else { - // Remove the block from the track - new TrackRippleRemoveBlockCommand(original_track, - b, - command); - - if (previous_is_gap && next_is_gap) { - // Clip is surrounded by gaps, merge both together - - // Remove one of the gaps - new TrackRippleRemoveBlockCommand(original_track, - b->next(), - command); - - // Resize the other to match both - new BlockResizeCommand(b->previous(), - b->previous()->length() + b->length() + b->next()->length(), - command); - } else { - // Resize the surrounding block to take its place - Block* gap_to_resize = previous_is_gap ? b->previous() : b->next(); - new BlockResizeCommand(gap_to_resize, - gap_to_resize->length() + b->length(), - command); - } - } + new TrackReplaceBlockCommand(original_track, + b, + gap, + command); if (remove_from_graph) { new NodeRemoveWithExclusiveDeps(static_cast(b->parent()), b, command); diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 272b03b8d..6361e2d5a 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -162,6 +162,9 @@ void TimelineWidget::PointerTool::MouseReleaseInternal(TimelineViewMouseEvent *e QUndoCommand* command = new QUndoCommand(); QList blocks_to_temp_remove; + QList tracks_affected; + + bool duplicate_clips = (event->GetModifiers() & Qt::AltModifier); // Since all the ghosts will be leaving their old position in some way, we replace all of them with gaps here so the // entire timeline isn't disrupted in the process @@ -175,9 +178,17 @@ void TimelineWidget::PointerTool::MouseReleaseInternal(TimelineViewMouseEvent *e Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); - blocks_to_temp_remove.append(b); + // If we're duplicating (user is holding ALT), no need to remove the original clip. However if the ghost was + // trimmed, it can't be duplicated. + if (!duplicate_clips || ghost->mode() != Timeline::kMove) { + blocks_to_temp_remove.append(b); + } + + tracks_affected.append(ghost->Track()); + tracks_affected.append(ghost->GetAdjustedTrack()); } + // If there are any blocks to remove, remove them parent()->DeleteSelectedInternal(blocks_to_temp_remove, false, command); // Now we place the clips back in the timeline where the user moved them. It's legal for them to overwrite parts or @@ -190,6 +201,8 @@ void TimelineWidget::PointerTool::MouseReleaseInternal(TimelineViewMouseEvent *e continue; } + const TrackReference& track_ref = ghost->GetAdjustedTrack(); + Block* b = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); // Normal blocks work in conjunction with the gap made above @@ -202,9 +215,19 @@ void TimelineWidget::PointerTool::MouseReleaseInternal(TimelineViewMouseEvent *e } else { new BlockResizeCommand(b, ghost->AdjustedLength(), command); } - } + } else if (duplicate_clips && ghost->mode() == Timeline::kMove) { + // Duplicate rather than move + Node* copy = b->copy(); - const TrackReference& track_ref = ghost->GetAdjustedTrack(); + new NodeAddCommand(static_cast(b->parent()), + copy, + command); + + new NodeCopyInputsCommand(b, copy, true, command); + + // Place the copy instead of the original block + b = static_cast(copy); + } new TrackPlaceBlockCommand(parent()->timeline_node_->track_list(track_ref.type()), track_ref.index(), @@ -213,6 +236,14 @@ void TimelineWidget::PointerTool::MouseReleaseInternal(TimelineViewMouseEvent *e command); } + if (command->childCount() > 0) { + foreach (const TrackReference& t, tracks_affected) { + new TrackCleanGapsCommand(parent()->timeline_node_->track_list(t.type()), + t.index(), + command); + } + } + Core::instance()->undo_stack()->pushIfHasChildren(command); } @@ -360,6 +391,7 @@ void TimelineWidget::PointerTool::InitiateGhosts(TimelineViewBlockItem* clicked_ // For each selected item, create a "ghost", a visual representation of the action before it gets performed foreach (TimelineViewBlockItem* clip_item, clips) { + // Determine correct mode for ghost // // Movement is indiscriminate, all the ghosts can be set to do this, however trimming is limited to one block @@ -367,6 +399,12 @@ void TimelineWidget::PointerTool::InitiateGhosts(TimelineViewBlockItem* clicked_ bool include_this_clip = true; + if (clip_item->block()->type() == Block::kGap + && trim_mode != Timeline::kTrimIn + && trim_mode != Timeline::kTrimOut) { + continue; + } + if (clip_item != clicked_item && (trim_mode == Timeline::kTrimIn || trim_mode == Timeline::kTrimOut)) { include_this_clip = multitrim_enabled ? IsClipTrimmable(clip_item, clips, trim_mode) : false; @@ -376,6 +414,7 @@ void TimelineWidget::PointerTool::InitiateGhosts(TimelineViewBlockItem* clicked_ Block* block = clip_item->block(); Timeline::MovementMode block_mode = trim_mode; + // If we don't allow gap trimming, we automatically switch to the next/previous block to trim if (block->type() == Block::kGap && !allow_gap_trimming) { if (trim_mode == Timeline::kTrimIn) { // Trim the previous clip's out point instead diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp index 9689b56e4..b22e902db 100644 --- a/app/widget/timelinewidget/undo/undo.cpp +++ b/app/widget/timelinewidget/undo/undo.cpp @@ -123,13 +123,13 @@ void BlockSetMediaOutCommand::undo() TrackRippleRemoveBlockCommand::TrackRippleRemoveBlockCommand(TrackOutput *track, Block *block, QUndoCommand *parent) : QUndoCommand(parent), track_(track), - block_(block), - before_(block->previous()) + block_(block) { } void TrackRippleRemoveBlockCommand::redo() { + before_ = block_->previous(); track_->RippleRemoveBlock(block_); } @@ -217,7 +217,7 @@ void TrackRippleRemoveAreaCommand::redo() static_cast(track_->parent())->AddNode(copy); Node::CopyInputs(splice_, copy); - copy->set_length_and_media_in(splice_->length() - (out_ - splice_->in())); + copy->set_length_and_media_in(splice_original_length_ - (out_ - splice_->in())); track_->InsertBlockAfter(copy, splice_); @@ -296,6 +296,7 @@ void TrackRippleRemoveAreaCommand::undo() track_->InsertBlockBefore(remove_block, trim_in_); } } + removed_blocks_.clear(); // If we picked up a block to trim the in point of if (trim_in_old_length_ != trim_in_new_length_) { @@ -552,3 +553,88 @@ BlockSplitPreservingLinksCommand::BlockSplitPreservingLinksCommand(const QVector } } } + +TrackCleanGapsCommand::TrackCleanGapsCommand(TrackList *track_list, int index, QUndoCommand *parent) : + QUndoCommand(parent), + track_list_(track_list), + track_index_(index) +{ +} + +void TrackCleanGapsCommand::redo() +{ + GapBlock* on_gap = nullptr; + QList consecutive_gaps; + + TrackOutput* track = track_list_->TrackAt(track_index_); + + foreach (Block* b, track->Blocks()) { + if (b) { + if (b->type() == Block::kGap) { + if (on_gap) { + consecutive_gaps.append(static_cast(b)); + } else { + on_gap = static_cast(b); + } + } else if (on_gap) { + merged_gaps_.append({on_gap, on_gap->length(), consecutive_gaps}); + + // Remove each gap and add to the length of the merged + // We can block the IC signal because merging gaps won't actually change anything + track->BlockInvalidateCache(); + rational new_gap_length = on_gap->length(); + foreach (GapBlock* gap, consecutive_gaps) { + track->RippleRemoveBlock(gap); + + new_gap_length += gap->length(); + } + on_gap->set_length(new_gap_length); + track->UnblockInvalidateCache(); + + // Reset state + on_gap = nullptr; + consecutive_gaps.clear(); + } + } + } + + if (on_gap) { + // If we're here, we found at least one or several + removed_end_gaps_.append(on_gap); + removed_end_gaps_.append(consecutive_gaps); + + foreach (GapBlock* gap, removed_end_gaps_) { + track->RippleRemoveBlock(gap); + } + } +} + +void TrackCleanGapsCommand::undo() +{ + TrackOutput* track = track_list_->TrackAt(track_index_); + + // Restored removed end gaps + foreach (GapBlock* gap, removed_end_gaps_) { + track->AppendBlock(gap); + } + removed_end_gaps_.clear(); + + track->BlockInvalidateCache(); + + for (int i=merged_gaps_.size()-1;i>=0;i--) { + const MergedGap& merge_info = merged_gaps_.at(i); + + merge_info.merged->set_length(merge_info.original_length); + + GapBlock* last_gap_added = merge_info.merged; + + foreach (GapBlock* gap, merge_info.removed) { + track->InsertBlockAfter(gap, last_gap_added); + last_gap_added = gap; + } + } + + track->UnblockInvalidateCache(); + + merged_gaps_.clear(); +} diff --git a/app/widget/timelinewidget/undo/undo.h b/app/widget/timelinewidget/undo/undo.h index 3a6f3097c..0eadf57bf 100644 --- a/app/widget/timelinewidget/undo/undo.h +++ b/app/widget/timelinewidget/undo/undo.h @@ -255,4 +255,30 @@ private: Block* replace_; }; +class TrackCleanGapsCommand : public QUndoCommand { +public: + TrackCleanGapsCommand(TrackList* track_list, int index, QUndoCommand* parent = nullptr); + + virtual void redo() override; + virtual void undo() override; + +private: + struct MergedGap { + GapBlock* merged; + rational original_length; + QList removed; + }; + + TrackList* track_list_; + + int track_index_; + + QObject memory_manager_; + + QList merged_gaps_; + + QList removed_end_gaps_; + +}; + #endif // TIMELINEUNDOABLE_H diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 203596bbb..37f188605 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -180,6 +180,10 @@ void TimelineView::drawBackground(QPainter *painter, const QRectF &rect) int line_y = 0; foreach (TrackOutput* track, connected_track_list_->Tracks()) { + if (!track) { + continue; + } + line_y += track->GetTrackHeight(); // One px gap between tracks