improved a number of pointer tool functions and fixed a number of bugs
This commit is contained in:
@@ -143,6 +143,8 @@ void TrackList::RemoveTrack()
|
||||
GetParentGraph()->TakeNode(track);
|
||||
|
||||
delete track;
|
||||
|
||||
track_input_->RemoveLast();
|
||||
}
|
||||
|
||||
void TrackList::TrackConnected(NodeEdgePtr edge)
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -389,60 +389,20 @@ void TimelineWidget::SplitAtPlayhead()
|
||||
void TimelineWidget::DeleteSelectedInternal(const QList<Block *> 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<NodeGraph*>(b->parent()),
|
||||
gap,
|
||||
command);
|
||||
new NodeAddCommand(static_cast<NodeGraph*>(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<NodeGraph*>(b->parent()), b, command);
|
||||
|
||||
@@ -162,6 +162,9 @@ void TimelineWidget::PointerTool::MouseReleaseInternal(TimelineViewMouseEvent *e
|
||||
QUndoCommand* command = new QUndoCommand();
|
||||
|
||||
QList<Block*> blocks_to_temp_remove;
|
||||
QList<TrackReference> 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<Block>(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<Block>(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<NodeGraph*>(b->parent()),
|
||||
copy,
|
||||
command);
|
||||
|
||||
new NodeCopyInputsCommand(b, copy, true, command);
|
||||
|
||||
// Place the copy instead of the original block
|
||||
b = static_cast<Block*>(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
|
||||
|
||||
@@ -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<NodeGraph*>(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<GapBlock*> 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<GapBlock*>(b));
|
||||
} else {
|
||||
on_gap = static_cast<GapBlock*>(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();
|
||||
}
|
||||
|
||||
@@ -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<GapBlock*> removed;
|
||||
};
|
||||
|
||||
TrackList* track_list_;
|
||||
|
||||
int track_index_;
|
||||
|
||||
QObject memory_manager_;
|
||||
|
||||
QList<MergedGap> merged_gaps_;
|
||||
|
||||
QList<GapBlock*> removed_end_gaps_;
|
||||
|
||||
};
|
||||
|
||||
#endif // TIMELINEUNDOABLE_H
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user