diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 3f0feab3f..63706f918 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -59,9 +59,16 @@ const rational &Block::out() return out_point_; } +const rational& Block::length() +{ + return length_; +} + void Block::set_length(const rational &length) { - Q_UNUSED(length) + length_ = length; + + RefreshFollowing(); } Block *Block::previous() diff --git a/app/node/block/block.h b/app/node/block/block.h index 42cb48baa..33b386c0d 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -41,6 +41,8 @@ public: kEnd }; + virtual Block* copy() = 0; + virtual Type type() = 0; virtual QString Category() override; @@ -48,8 +50,8 @@ public: const rational& in(); const rational& out(); - virtual rational length() = 0; - virtual void set_length(const rational& length); + virtual const rational &length(); + virtual void set_length(const rational &length); virtual Block* previous(); virtual Block* next(); @@ -106,6 +108,8 @@ private: rational in_point_; rational out_point_; + rational length_; + private slots: void BlockOrderChanged(NodeEdgePtr edge); diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 9baa42c3c..f3e37ecd4 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -29,6 +29,19 @@ ClipBlock::ClipBlock() AddParameter(texture_input_); } +Block *ClipBlock::copy() +{ + ClipBlock* c = new ClipBlock(); + + // Duplicate connection + // FIXME: This behavior should probably be configurable + if (texture_input()->IsConnected()) { + NodeParam::ConnectEdge(texture_input()->edges().first()->output(), c->texture_input()); + } + + return new ClipBlock(); +} + Block::Type ClipBlock::type() { return kClip; @@ -49,18 +62,6 @@ QString ClipBlock::Description() return tr("A time-based node that represents a media source."); } -rational ClipBlock::length() -{ - return length_; -} - -void ClipBlock::set_length(const rational &length) -{ - length_ = length; - - RefreshFollowing(); -} - NodeInput *ClipBlock::texture_input() { return texture_input_; diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index e247e039b..6a459082b 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -32,15 +32,14 @@ class ClipBlock : public Block public: ClipBlock(); + virtual Block* copy() override; + virtual Type type() override; virtual QString Name() override; virtual QString id() override; virtual QString Description() override; - virtual rational length() override; - virtual void set_length(const rational &length) override; - NodeInput* texture_input(); public slots: @@ -51,7 +50,6 @@ private: rational media_in_; - rational length_; }; #endif // TIMELINEBLOCK_H diff --git a/app/node/block/gap/gap.cpp b/app/node/block/gap/gap.cpp index 7f480e5a9..671bba1fc 100644 --- a/app/node/block/gap/gap.cpp +++ b/app/node/block/gap/gap.cpp @@ -24,6 +24,11 @@ GapBlock::GapBlock() { } +Block *GapBlock::copy() +{ + return new GapBlock(); +} + Block::Type GapBlock::type() { return kGap; @@ -43,15 +48,3 @@ QString GapBlock::Description() { return tr("A time-based node that represents an empty space."); } - -rational GapBlock::length() -{ - return length_; -} - -void GapBlock::set_length(const rational &length) -{ - length_ = length; - - RefreshFollowing(); -} diff --git a/app/node/block/gap/gap.h b/app/node/block/gap/gap.h index eedf97582..4dffb36bd 100644 --- a/app/node/block/gap/gap.h +++ b/app/node/block/gap/gap.h @@ -32,17 +32,16 @@ class GapBlock : public Block public: GapBlock(); + virtual Block * copy() override; + virtual Type type() override; virtual QString Name() override; virtual QString id() override; virtual QString Description() override; - virtual rational length() override; - virtual void set_length(const rational &length) override; - private: - rational length_; + }; #endif // TIMELINEBLOCK_H diff --git a/app/node/graph.cpp b/app/node/graph.cpp index 6a99cd7b5..96ce5a8ba 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -53,13 +53,13 @@ void NodeGraph::AddNodeWithDependencies(Node *node) } } -void NodeGraph::RemoveNode(Node *node) +void NodeGraph::TakeNode(Node *node, QObject* new_parent) { if (!ContainsNode(node)) { return; } - node->setParent(nullptr); + node->setParent(new_parent); disconnect(node, SIGNAL(EdgeAdded(NodeEdgePtr)), this, SIGNAL(EdgeAdded(NodeEdgePtr))); disconnect(node, SIGNAL(EdgeRemoved(NodeEdgePtr)), this, SIGNAL(EdgeRemoved(NodeEdgePtr))); @@ -67,6 +67,21 @@ void NodeGraph::RemoveNode(Node *node) emit NodeRemoved(node); } +QList NodeGraph::TakeNodeWithItsDependencies(Node *node, QObject *new_parent) +{ + if (!ContainsNode(node)) { + return QList(); + } + + QList deps = node->GetExclusiveDependencies(); + + foreach (Node* d, deps) { + TakeNode(d, new_parent); + } + + return deps; +} + QList NodeGraph::nodes() { return static_qobjectlist_cast(children()); diff --git a/app/node/graph.h b/app/node/graph.h index 84010d172..843eb8274 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -49,14 +49,26 @@ public: * @brief Adds a node to this graph and all nodes connected to its inputs * * Adds the Node to the graph and runs through its inputs adding all of its dependencies (and all of their - * dependencies and so forth). + * dependencies and so forth). The graph takes ownershi of all Nodes added through this process. */ void AddNodeWithDependencies(Node* node); /** - * @brief Removes a Node from the graph BUT doesn't destroy it. Ownership is returned to the caller. + * @brief Removes a Node from the graph BUT doesn't destroy it. Ownership is passed to `new_parent`. */ - void TakeNode(Node* node); + void TakeNode(Node* node, QObject* new_parent = nullptr); + + /** + * @brief Removes a Node from the graph and its dependencies (ONLY if the dependencies are exclusive to this Node). + * + * Returns a list of all Nodes that were removed in this process (except the Node used as a parameter) + * + * Only dependencies that are exclusively dependencies of this Node are removed. If a dependency Node is also + * used as the dependency of another Node, it is not removed and not returned in the list. + * + * Ownership of all Nodes is passed to `new_parent`. + */ + QList TakeNodeWithItsDependencies(Node* node, QObject* new_parent = nullptr); /** * @brief Retrieve a complete list of the nodes belonging to this graph diff --git a/app/node/node.cpp b/app/node/node.cpp index 255b88c00..9ebd573d8 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -119,6 +119,41 @@ QList Node::GetDependencies() return node_list; } +QList Node::GetExclusiveDependencies() +{ + QList deps = GetDependencies(); + + // Filter out any dependencies that are used elsewhere + for (int i=0;i params = deps.at(i)->parameters(); + + // See if any of this Node's outputs are used outside of this dep list + for (int j=0;jtype() == NodeParam::kOutput) { + QVector edges = p->edges(); + + for (int k=0;kinput()->parent())) { + deps.removeAt(i); + + i--; // -1 since we just removed a Node here + j = params.size(); // No need to keep looking at this Node's params + break; // Or this param's edges + } + } + } + } + } + + return deps; +} + QVariant Node::PtrToValue(void *ptr) { return reinterpret_cast(ptr); diff --git a/app/node/node.h b/app/node/node.h index dfb83dd4f..bb479032a 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -113,6 +113,14 @@ public: */ QList GetDependencies(); + /** + * @brief Returns a list of Nodes that this Node is dependent on, provided no other Nodes are dependent on them + * outside of this hierarchy. + * + * Similar to GetDependencies(), but excludes any Nodes that are used outside the dependency graph of this Node. + */ + QList GetExclusiveDependencies(); + /** * @brief Convert a pointer to a value that can be sent between NodeParams */ diff --git a/app/node/output/timeline/timeline.cpp b/app/node/output/timeline/timeline.cpp index a9639dd80..6e9939bed 100644 --- a/app/node/output/timeline/timeline.cpp +++ b/app/node/output/timeline/timeline.cpp @@ -26,7 +26,6 @@ #include "node/graph.h" TimelineOutput::TimelineOutput() : - first_block_(nullptr), current_block_(this), attached_timeline_(nullptr) { @@ -37,6 +36,11 @@ Block::Type TimelineOutput::type() return kEnd; } +Block *TimelineOutput::copy() +{ + return new TimelineOutput(); +} + QString TimelineOutput::Name() { return tr("Timeline"); @@ -89,14 +93,16 @@ void TimelineOutput::AttachTimeline(TimelinePanel *timeline) previous_block = previous_block->previous(); } - connect(attached_timeline_, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SLOT(InsertBlockAtIndex(Block*, int))); - connect(attached_timeline_, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SLOT(PlaceBlock(Block*, rational))); + TimelineView* view = attached_timeline_->view(); + + connect(view, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SLOT(InsertBlockAtIndex(Block*, int))); + connect(view, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SLOT(PlaceBlock(Block*, rational))); } } -rational TimelineOutput::length() +void TimelineOutput::set_length(const rational &) { - return 0; + // Prevent length changing on this Block } void TimelineOutput::Refresh() @@ -167,13 +173,9 @@ void TimelineOutput::InsertBlockBetweenBlocks(Block *block, Block *before, Block Block::ConnectBlocks(block, after); } -Block *TimelineOutput::first_block() +void TimelineOutput::InsertBlockAfter(Block *block, Block *before) { - if (block_cache_.isEmpty()) { - return nullptr; - } - - return block_cache_.first(); + InsertBlockBetweenBlocks(block, before, before->next()); } Block *TimelineOutput::attached_block() @@ -248,6 +250,7 @@ void TimelineOutput::PlaceBlock(Block *block, rational start) { AddBlockToGraph(block); + // Place block at the beginning if (start == 0) { // FIXME: Remove existing @@ -256,15 +259,16 @@ void TimelineOutput::PlaceBlock(Block *block, rational start) } // Check if the placement location is past the end of the timeline - if (start > in()) { - // FIXME: Remove existing + if (start >= in()) { + if (start > in()) { + // If so, insert a gap here + GapBlock* gap = new GapBlock(); + gap->set_length(start - in()); - // If so, insert a gap here - GapBlock* gap = new GapBlock(); - gap->set_length(start - in()); + // Then append them + AppendBlock(gap); + } - // Then append them - AppendBlock(gap); AppendBlock(block); return; @@ -284,3 +288,74 @@ void TimelineOutput::PlaceBlock(Block *block, rational start) } } } + +void TimelineOutput::RemoveBlock(Block *block) +{ + GapBlock* gap = new GapBlock(); + gap->set_length(block->length()); + + Block* previous = block->previous(); + Block* next = block->next(); + + // Remove block + RippleRemoveBlock(block); + + if (previous == nullptr) { + // Block must be at the beginning + PrependBlock(gap); + } else { + InsertBlockBetweenBlocks(gap, previous, next); + } +} + +void TimelineOutput::RippleRemoveBlock(Block *block) +{ + Block* previous = block->previous(); + Block* next = block->next(); + + if (previous != nullptr) { + Block::DisconnectBlocks(previous, block); + } + + if (next != nullptr) { + Block::DisconnectBlocks(block, next); + } + + if (previous != nullptr && next != nullptr) { + Block::ConnectBlocks(previous, next); + } +} + +void TimelineOutput::SplitBlock(Block *block, rational time) +{ + if (time < block->in() || time >= block->out()) { + return; + } + + rational original_length = block->length(); + + block->set_length(time - block->in()); + + Block* copy = block->copy(); + copy->set_length(original_length - block->length()); + InsertBlockAfter(copy, block); +} + +void TimelineOutput::SpliceBlock(Block *inner, Block *outer, rational inner_in) +{ + Q_ASSERT(inner_in >= outer->in() && inner_in < outer->out()); + + // Cache original length + rational original_length = outer->length(); + + // Set outer clip to the clip that PRECEDES the inner clip + outer->set_length(inner_in - outer->in()); + + // Insert inner clip between BEFORE clip and its next clip + InsertBlockAfter(inner, outer); + + // Create the AFTER clip + Block* copy = outer->copy(); + copy->set_length(original_length - outer->length() - inner->length()); + InsertBlockAfter(copy, inner); +} diff --git a/app/node/output/timeline/timeline.h b/app/node/output/timeline/timeline.h index 54c652878..89f14efe9 100644 --- a/app/node/output/timeline/timeline.h +++ b/app/node/output/timeline/timeline.h @@ -35,6 +35,8 @@ public: virtual Type type() override; + virtual Block* copy() override; + virtual QString Name() override; virtual QString id() override; virtual QString Category() override; @@ -42,7 +44,7 @@ public: void AttachTimeline(TimelinePanel* timeline); - virtual rational length() override; + virtual void set_length(const rational &length) override; virtual void Refresh() override; @@ -67,13 +69,10 @@ private: */ void AddBlockToGraph(Block* block); - QVector block_cache_; - - Block* first_block(); - Block* attached_block(); - Block* first_block_; + QVector block_cache_; + Block* current_block_; TimelinePanel* attached_timeline_; @@ -99,6 +98,13 @@ private slots: */ void InsertBlockBetweenBlocks(Block* block, Block* before, Block* after); + /** + * @brief Inserts Block after another Block + * + * Equivalent to calling InsertBlockBetweenBlocks(block, before, before->next()) + */ + void InsertBlockAfter(Block* block, Block* before); + /** * @brief Adds Block `block` at the very end of the Sequence after all other clips */ @@ -124,14 +130,15 @@ private slots: void RippleRemoveBlock(Block* block); /** - * @brief Removes the Block at the given index pushing all subsequent Blocks earlier to take up the space + * @brief Splits `block` into two Blocks at the Sequence point `time` */ - void RippleRemoveBlockAtIndex(int index); + void SplitBlock(Block* block, rational time); /** - * @brief Removes the last Block of the Sequence at the given index + * @brief Inserts Block `inner` between Block `outer`, splitting and shortening it to fit without changing the overall + * length */ - void RippleRemoveLast(); + void SpliceBlock(Block* inner, Block* outer, rational inner_in); }; #endif // TIMELINEOUTPUT_H diff --git a/app/node/param.cpp b/app/node/param.cpp index 54d8a08fd..5e59619f7 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -57,6 +57,11 @@ int NodeParam::index() return parent()->IndexOfParameter(this); } +bool NodeParam::IsConnected() +{ + return !edges_.isEmpty(); +} + const QVector &NodeParam::edges() { return edges_; diff --git a/app/node/param.h b/app/node/param.h index 420387408..eaf718694 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -106,6 +106,11 @@ public: */ int index(); + /** + * @brief Returns whether anything is connected to this parameter or not + */ + bool IsConnected(); + /** * @brief Return a list of edges (aka connections to other nodes) * diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index e40b12c16..c0056fbdc 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -37,8 +37,6 @@ TimelinePanel::TimelinePanel(QWidget *parent) : layout->addWidget(ruler_); view_ = new TimelineView(this); - connect(view_, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SIGNAL(RequestInsertBlockAtIndex(Block*, int))); - connect(view_, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SIGNAL(RequestPlaceBlock(Block*, rational))); layout->addWidget(view_); connect(view_->horizontalScrollBar(), SIGNAL(valueChanged(int)), ruler_, SLOT(SetScroll(int))); diff --git a/app/project/item/footage/stream.h b/app/project/item/footage/stream.h index ef4661176..b99071dc6 100644 --- a/app/project/item/footage/stream.h +++ b/app/project/item/footage/stream.h @@ -88,4 +88,7 @@ private: using StreamPtr = std::shared_ptr; +#include +Q_DECLARE_METATYPE(StreamPtr) + #endif // STREAM_H diff --git a/app/widget/timelineview/timelineview.cpp b/app/widget/timelineview/timelineview.cpp index 8250013ae..565ae9376 100644 --- a/app/widget/timelineview/timelineview.cpp +++ b/app/widget/timelineview/timelineview.cpp @@ -198,7 +198,11 @@ void TimelineView::ClearGhosts() void TimelineView::BlockChanged() { - clip_items_[static_cast(sender())]->UpdateRect(); + TimelineViewRect* rect = clip_items_[static_cast(sender())]; + + if (rect != nullptr) { + rect->UpdateRect(); + } } TimelineView::Tool::Tool(TimelineView *parent) : diff --git a/app/widget/timelineview/timelineviewghostitem.cpp b/app/widget/timelineview/timelineviewghostitem.cpp index ef57c99e4..a1e1f77b3 100644 --- a/app/widget/timelineview/timelineviewghostitem.cpp +++ b/app/widget/timelineview/timelineviewghostitem.cpp @@ -40,6 +40,16 @@ const rational &TimelineViewGhostItem::Out() return out_; } +rational TimelineViewGhostItem::Length() +{ + return out_ - in_; +} + +rational TimelineViewGhostItem::AdjustedLength() +{ + return GetAdjustedOut() - GetAdjustedIn(); +} + void TimelineViewGhostItem::SetIn(const rational &in) { in_ = in; @@ -78,14 +88,14 @@ rational TimelineViewGhostItem::GetAdjustedOut() return out_ + out_adj_; } -StreamPtr TimelineViewGhostItem::stream() +const QVariant &TimelineViewGhostItem::data() { - return stream_; + return data_; } -void TimelineViewGhostItem::SetStream(StreamPtr f) +void TimelineViewGhostItem::SetData(const QVariant &data) { - stream_ = f; + data_ = data; } void TimelineViewGhostItem::UpdateRect() diff --git a/app/widget/timelineview/timelineviewghostitem.h b/app/widget/timelineview/timelineviewghostitem.h index 1efc4deb3..393ef87a7 100644 --- a/app/widget/timelineview/timelineviewghostitem.h +++ b/app/widget/timelineview/timelineviewghostitem.h @@ -21,6 +21,8 @@ #ifndef TIMELINEVIEWGHOSTITEM_H #define TIMELINEVIEWGHOSTITEM_H +#include + #include "project/item/footage/footage.h" #include "timelineviewrect.h" @@ -35,6 +37,9 @@ public: const rational& In(); const rational& Out(); + rational Length(); + rational AdjustedLength(); + void SetIn(const rational& in); void SetOut(const rational& out); @@ -44,8 +49,8 @@ public: rational GetAdjustedIn(); rational GetAdjustedOut(); - StreamPtr stream(); - void SetStream(StreamPtr f); + const QVariant& data(); + void SetData(const QVariant& data); virtual void UpdateRect() override; @@ -59,6 +64,8 @@ private: rational out_adj_; StreamPtr stream_; + + QVariant data_; }; #endif // TIMELINEVIEWGHOSTITEM_H diff --git a/app/widget/timelineview/tool/import.cpp b/app/widget/timelineview/tool/import.cpp index be2708930..9e6d6a8da 100644 --- a/app/widget/timelineview/tool/import.cpp +++ b/app/widget/timelineview/tool/import.cpp @@ -47,6 +47,9 @@ void TimelineView::ImportTool::DragEnter(QDragEnterEvent *event) quintptr item_ptr; int r; + // Set ghosts to start where the cursor entered + rational ghost_start = parent()->ScreenToTime(event->pos().x()); + while (!stream.atEnd()) { stream >> r >> item_ptr; @@ -65,14 +68,19 @@ void TimelineView::ImportTool::DragEnter(QDragEnterEvent *event) rational footage_duration(stream->timebase().numerator() * stream->duration(), stream->timebase().denominator()); - ghost->SetIn(0); - ghost->SetOut(footage_duration); - ghost->SetStream(stream); + ghost->SetIn(ghost_start); + ghost->SetOut(ghost_start + footage_duration); + ghost->SetData(QVariant::fromValue(stream)); parent()->AddGhost(ghost); + + // Stack each ghost one after the other + ghost_start += footage_duration; } } + drag_start_ = event->pos(); + event->accept(); } else { // FIXME: Implement dropping from file @@ -85,10 +93,12 @@ void TimelineView::ImportTool::DragMove(QDragMoveEvent *event) if (parent()->HasGhosts()) { // Move ghosts to the mouse cursor foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { - rational time = parent()->ScreenToTime(event->pos().x()); + QPoint movement = event->pos() - drag_start_; - ghost->SetOut(ghost->Out() - ghost->In() + time); - ghost->SetIn(time); + rational time_movement = parent()->ScreenToTime(movement.x()); + + ghost->SetInAdjustment(time_movement); + ghost->SetOutAdjustment(time_movement); } event->accept(); @@ -125,15 +135,15 @@ void TimelineView::ImportTool::DragDrop(QDropEvent *event) clip->setParent(&node_memory_manager); media->setParent(&node_memory_manager); - clip->set_length(ghost->Out() - ghost->In()); - media->SetFootage(ghost->stream()->footage()); + clip->set_length(ghost->Length()); + media->SetFootage(ghost->data().value()->footage()); NodeParam::ConnectEdge(media->texture_output(), clip->texture_input()); if (event->keyboardModifiers() & Qt::ControlModifier) { - emit parent()->RequestPlaceBlock(clip, ghost->In()); + emit parent()->RequestInsertBlockAtTime(clip, ghost->GetAdjustedIn()); } else { - emit parent()->RequestInsertBlockAtTime(clip, ghost->In()); + emit parent()->RequestPlaceBlock(clip, ghost->GetAdjustedIn()); } } diff --git a/app/widget/timelineview/tool/pointer.cpp b/app/widget/timelineview/tool/pointer.cpp index cbd1f06b1..19af5dccd 100644 --- a/app/widget/timelineview/tool/pointer.cpp +++ b/app/widget/timelineview/tool/pointer.cpp @@ -51,6 +51,7 @@ void TimelineView::PointerTool::MouseMove(QMouseEvent *event) parent()->QGraphicsView::mouseMoveEvent(event); if (!dragging_) { + // Let's see if there's anything selected to drag if (parent()->itemAt(event->pos()) != nullptr) { QList selected_items = parent()->scene_.selectedItems(); @@ -76,6 +77,7 @@ void TimelineView::PointerTool::MouseMove(QMouseEvent *event) } dragging_ = true; + } else if (!parent()->ghost_items_.isEmpty()) { QPoint movement = event->pos() - drag_start_; @@ -103,6 +105,12 @@ void TimelineView::PointerTool::MouseRelease(QMouseEvent *event) parent()->QGraphicsView::mouseReleaseEvent(event); + foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + Block* b = Node::ValueToPtr(ghost->data()); + + emit parent()->RequestPlaceBlock(b, ghost->In()); + } + parent()->ClearGhosts(); dragging_ = false;