work towards making timeline actions all undoable
This commit is contained in:
@@ -75,6 +75,15 @@ void Block::set_length(const rational &length)
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void Block::set_length_and_media_in(const rational &length)
|
||||
{
|
||||
// Calculate media_in adjustment
|
||||
set_media_in(media_in_ + (length_ - length));
|
||||
|
||||
// Set the length
|
||||
set_length(length);
|
||||
}
|
||||
|
||||
Block *Block::previous()
|
||||
{
|
||||
return ValueToPtr<Block>(previous_input_->get_value(0));
|
||||
@@ -171,12 +180,16 @@ const rational &Block::media_in()
|
||||
|
||||
void Block::set_media_in(const rational &media_in)
|
||||
{
|
||||
Lock();
|
||||
|
||||
if (media_in_ != media_in) {
|
||||
media_in_ = media_in;
|
||||
|
||||
// Signal that this clips contents have changed
|
||||
SendInvalidateCache(in(), out());
|
||||
}
|
||||
|
||||
Unlock();
|
||||
}
|
||||
|
||||
const QString &Block::block_name()
|
||||
|
||||
@@ -50,8 +50,9 @@ public:
|
||||
const rational& in();
|
||||
const rational& out();
|
||||
|
||||
virtual const rational &length();
|
||||
virtual void set_length(const rational &length);
|
||||
const rational &length();
|
||||
void set_length(const rational &length);
|
||||
void set_length_and_media_in(const rational &length);
|
||||
|
||||
Block* previous();
|
||||
Block* next();
|
||||
|
||||
+3
-1
@@ -59,11 +59,13 @@ void NodeGraph::TakeNode(Node *node, QObject* new_parent)
|
||||
return;
|
||||
}
|
||||
|
||||
node->setParent(new_parent);
|
||||
node->DisconnectAll();
|
||||
|
||||
disconnect(node, SIGNAL(EdgeAdded(NodeEdgePtr)), this, SIGNAL(EdgeAdded(NodeEdgePtr)));
|
||||
disconnect(node, SIGNAL(EdgeRemoved(NodeEdgePtr)), this, SIGNAL(EdgeRemoved(NodeEdgePtr)));
|
||||
|
||||
node->setParent(new_parent);
|
||||
|
||||
emit NodeRemoved(node);
|
||||
}
|
||||
|
||||
|
||||
@@ -347,6 +347,15 @@ bool Node::HasConnectedOutputs()
|
||||
return HasParamOfType(NodeParam::kOutput, true);
|
||||
}
|
||||
|
||||
void Node::DisconnectAll()
|
||||
{
|
||||
QList<NodeParam*> param = parameters();
|
||||
|
||||
for (int i=0;i<param.size();i++) {
|
||||
param.at(i)->DisconnectAll();
|
||||
}
|
||||
}
|
||||
|
||||
void Node::Hash(QCryptographicHash *hash, NodeOutput* from, const rational &time)
|
||||
{
|
||||
// Add this Node's ID
|
||||
|
||||
@@ -173,6 +173,11 @@ public:
|
||||
*/
|
||||
bool HasConnectedOutputs();
|
||||
|
||||
/**
|
||||
* @brief Severs all input and output connections
|
||||
*/
|
||||
void DisconnectAll();
|
||||
|
||||
/**
|
||||
* @brief Add's unique information about this Node at the given time to a QCryptographicHash
|
||||
*/
|
||||
|
||||
@@ -81,6 +81,11 @@ const QVector<TrackOutput *> &TimelineOutput::Tracks()
|
||||
return track_cache_;
|
||||
}
|
||||
|
||||
TrackOutput *TimelineOutput::TrackAt(int index)
|
||||
{
|
||||
return track_cache_.at(index);
|
||||
}
|
||||
|
||||
QVariant TimelineOutput::Value(NodeOutput *output, const rational &time)
|
||||
{
|
||||
if (output == length_output_) {
|
||||
@@ -191,6 +196,19 @@ void TimelineOutput::AddTrack()
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineOutput::RemoveTrack()
|
||||
{
|
||||
if (track_cache_.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TrackOutput* track = track_cache_.last();
|
||||
|
||||
static_cast<NodeGraph*>(parent())->TakeNode(track);
|
||||
|
||||
delete track;
|
||||
}
|
||||
|
||||
TrackOutput *TimelineOutput::TrackFromBlock(Block *block)
|
||||
{
|
||||
Block* n = block;
|
||||
@@ -263,83 +281,3 @@ void TimelineOutput::TrackEdgeRemoved(NodeEdgePtr edge)
|
||||
DetachTrack(added_track);
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineOutput::PlaceBlock(Block *block, rational start, int track)
|
||||
{
|
||||
Q_ASSERT(track >= 0);
|
||||
|
||||
while (track >= track_cache_.size()) {
|
||||
AddTrack();
|
||||
}
|
||||
|
||||
track_cache_.at(track)->PlaceBlock(block, start);
|
||||
}
|
||||
|
||||
void TimelineOutput::ReplaceBlock(Block *old, Block *replace, int track)
|
||||
{
|
||||
track_cache_.at(track)->ReplaceBlock(old, replace);
|
||||
}
|
||||
|
||||
void TimelineOutput::SplitAtTime(rational time, int track)
|
||||
{
|
||||
if (track >= track_cache_.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
track_cache_.at(track)->SplitAtTime(time);
|
||||
}
|
||||
|
||||
void TimelineOutput::ResizeBlock(Block *block, rational new_length)
|
||||
{
|
||||
block->set_length(new_length);
|
||||
}
|
||||
|
||||
void TimelineOutput::RippleBlocks(QList<Block *> blocks, rational ripple_length, olive::timeline::MovementMode mode)
|
||||
{
|
||||
if (blocks.isEmpty()
|
||||
|| ripple_length == 0
|
||||
|| (mode != olive::timeline::kTrimIn && mode != olive::timeline::kTrimOut)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == olive::timeline::kTrimIn) {
|
||||
// Flip the ripple length if we're trimming the in point
|
||||
ripple_length = -ripple_length;
|
||||
}
|
||||
|
||||
QVector<TrackOutput*> rippled_tracks;
|
||||
|
||||
rational ripple_point = RATIONAL_MAX;
|
||||
|
||||
// Ripple each Block as requested
|
||||
foreach (Block* b, blocks) {
|
||||
if (mode == olive::timeline::kTrimIn) {
|
||||
ripple_point = qMin(ripple_point, b->in());
|
||||
|
||||
// Extend media in point
|
||||
b->set_media_in(b->media_in() - ripple_length);
|
||||
} else {
|
||||
ripple_point = qMin(ripple_point, b->out());
|
||||
}
|
||||
|
||||
b->set_length(b->length() + ripple_length);
|
||||
|
||||
rippled_tracks.append(TrackFromBlock(b));
|
||||
}
|
||||
|
||||
// For each track that did not have a rippled clip, insert a Gap to keep all tracks synchronized
|
||||
// FIXME: Assumes rippling out point further out
|
||||
foreach (TrackOutput* track, track_cache_) {
|
||||
|
||||
if (!rippled_tracks.contains(track)) {
|
||||
Block* block_after_time = track->NearestBlockAfter(ripple_point);
|
||||
|
||||
if (block_after_time != nullptr) {
|
||||
// Insert Gap block before this Block
|
||||
GapBlock* gap = new GapBlock();
|
||||
gap->set_length(ripple_length);
|
||||
track->InsertBlockBefore(gap, block_after_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,12 @@ public:
|
||||
|
||||
const QVector<TrackOutput*>& Tracks();
|
||||
|
||||
TrackOutput* TrackAt(int index);
|
||||
|
||||
void AddTrack();
|
||||
|
||||
void RemoveTrack();
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Slot for when the track connection is added
|
||||
@@ -79,31 +85,6 @@ public slots:
|
||||
*/
|
||||
void TrackEdgeRemoved(NodeEdgePtr edge);
|
||||
|
||||
/**
|
||||
* @brief Forwards a PlaceBlock signal to the requested track
|
||||
*
|
||||
* If the track index doesn't exist, tracks are automatically created until a track at that index does exist
|
||||
* (provided the track index is positive). A negative track index fails immediately.
|
||||
*/
|
||||
void PlaceBlock(Block* block, rational start, int track);
|
||||
|
||||
/**
|
||||
* @brief Forwards a ReplaceBlock signal to the appropriate track
|
||||
*/
|
||||
void ReplaceBlock(Block* old, Block* replace, int track);
|
||||
|
||||
/**
|
||||
* @brief Forwards a SplitAtTime signal to the appropriate track
|
||||
*/
|
||||
void SplitAtTime(rational time, int track);
|
||||
|
||||
/**
|
||||
* @brief Resizes a Block
|
||||
*/
|
||||
void ResizeBlock(Block* block, rational new_length);
|
||||
|
||||
void RippleBlocks(QList<Block*> blocks, rational ripple_length, olive::timeline::MovementMode mode);
|
||||
|
||||
signals:
|
||||
void TimebaseChanged(const rational &timebase);
|
||||
|
||||
@@ -129,8 +110,6 @@ private:
|
||||
|
||||
void DetachTrack(TrackOutput* track);
|
||||
|
||||
void AddTrack();
|
||||
|
||||
static TrackOutput* TrackFromBlock(Block* block);
|
||||
|
||||
NodeInput* track_input_;
|
||||
|
||||
@@ -71,11 +71,6 @@ QString TrackOutput::Description()
|
||||
"a Sequence.");
|
||||
}
|
||||
|
||||
void TrackOutput::set_length(const rational &)
|
||||
{
|
||||
// Prevent length changing on this Block
|
||||
}
|
||||
|
||||
void TrackOutput::Refresh()
|
||||
{
|
||||
QVector<Block*> detect_attached_blocks;
|
||||
@@ -337,31 +332,6 @@ void TrackOutput::UnblockInvalidateCache()
|
||||
block_invalidate_cache_stack_--;
|
||||
}
|
||||
|
||||
void TrackOutput::PlaceBlock(Block *block, rational start)
|
||||
{
|
||||
if (block_cache_.contains(block) && block->in() == start) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddBlockToGraph(block);
|
||||
|
||||
// Check if the placement location is past the end of the timeline
|
||||
if (start >= in()) {
|
||||
if (start > in()) {
|
||||
// If so, insert a gap here
|
||||
GapBlock* gap = new GapBlock();
|
||||
gap->set_length(start - in());
|
||||
AppendBlock(gap);
|
||||
}
|
||||
|
||||
AppendBlock(block);
|
||||
return;
|
||||
}
|
||||
|
||||
// Place the Block at this point
|
||||
RippleRemoveArea(start, start + block->length(), block);
|
||||
}
|
||||
|
||||
void TrackOutput::RemoveBlock(Block *block)
|
||||
{
|
||||
GapBlock* gap = new GapBlock();
|
||||
@@ -409,136 +379,6 @@ void TrackOutput::RippleRemoveBlock(Block *block)
|
||||
// FIXME: Should there be removing the Blocks from the graph?
|
||||
}
|
||||
|
||||
Block* TrackOutput::SplitBlock(Block *block, rational time)
|
||||
{
|
||||
if (time <= block->in() || time >= block->out()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BlockInvalidateCache();
|
||||
|
||||
rational original_length = block->length();
|
||||
|
||||
block->set_length(time - block->in());
|
||||
|
||||
Block* copy = block->copy();
|
||||
copy->set_length(original_length - block->length());
|
||||
copy->set_media_in(block->media_in() + block->length());
|
||||
|
||||
InsertBlockAfter(copy, block);
|
||||
|
||||
Node::CopyInputs(block, copy);
|
||||
|
||||
UnblockInvalidateCache();
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
void TrackOutput::SplitAtTime(rational time)
|
||||
{
|
||||
// Find Block that contains this time
|
||||
for (int i=0;i<block_cache_.size();i++) {
|
||||
Block* b = block_cache_.at(i);
|
||||
|
||||
if (b->out() == time) {
|
||||
// This time is between blocks, no split needs to occur
|
||||
return;
|
||||
} else if (b->in() < time && b->out() > time) {
|
||||
// We found the Block, split it
|
||||
SplitBlock(b, time);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TrackOutput::RippleRemoveArea(rational in, rational out, Block *insert)
|
||||
{
|
||||
// Block that needs to be split to remove this area
|
||||
Block* splice = nullptr;
|
||||
|
||||
// Block whose out point exceeds `in` and needs to be trimmed
|
||||
Block* trim_out_to_in = nullptr;
|
||||
|
||||
// Block whose in point exceeds `out` and needs to be trimmed
|
||||
Block* trim_in_to_out = nullptr;
|
||||
|
||||
// Blocks that are entirely within the area and need removing
|
||||
QList<Block*> remove;
|
||||
|
||||
// Iterate through blocks determining which need trimming/removing/splitting
|
||||
foreach (Block* block, block_cache_) {
|
||||
if (block->in() < in && block->out() > out) {
|
||||
// The area entirely within this Block
|
||||
splice = block;
|
||||
|
||||
// We don't need to do anything else here
|
||||
break;
|
||||
} else if (block->in() >= in && block->out() <= out) {
|
||||
// This Block's is entirely within the area
|
||||
remove.append(block);
|
||||
} else if (block->in() < in && block->out() >= in) {
|
||||
// This Block's out point exceeds `in`
|
||||
trim_out_to_in = block;
|
||||
} else if (block->in() <= out && block->out() > out) {
|
||||
// This Block's in point exceeds `out`
|
||||
trim_in_to_out = block;
|
||||
}
|
||||
}
|
||||
|
||||
BlockInvalidateCache();
|
||||
|
||||
// If we picked up a block to splice
|
||||
if (splice != nullptr) {
|
||||
|
||||
// Split the block here
|
||||
Block* copy = SplitBlock(splice, in);
|
||||
|
||||
// Perform all further actions as if we were just trimming these clips
|
||||
trim_out_to_in = splice;
|
||||
trim_in_to_out = copy;
|
||||
|
||||
}
|
||||
|
||||
// If we picked up a block to trim the in point of
|
||||
if (trim_in_to_out != nullptr && trim_in_to_out->in() < out) {
|
||||
rational new_length = trim_in_to_out->out() - out;
|
||||
|
||||
// Push media_in forward to compensate
|
||||
rational length_diff = trim_in_to_out->length() - new_length;
|
||||
trim_in_to_out->set_media_in(trim_in_to_out->media_in() - length_diff);
|
||||
|
||||
trim_in_to_out->set_length(new_length);
|
||||
}
|
||||
|
||||
// Remove all blocks that are flagged for removal
|
||||
foreach (Block* remove_block, remove) {
|
||||
RippleRemoveBlock(remove_block);
|
||||
}
|
||||
|
||||
// If we picked up a block to trim the out point of
|
||||
if (trim_out_to_in != nullptr && trim_out_to_in->out() > in) {
|
||||
trim_out_to_in->set_length(in - trim_out_to_in->in());
|
||||
}
|
||||
|
||||
// If we were given a block to insert, insert it here
|
||||
if (insert != nullptr) {
|
||||
if (trim_out_to_in == nullptr) {
|
||||
// This is the start of the Sequence
|
||||
PrependBlock(insert);
|
||||
} else if (trim_in_to_out == nullptr) {
|
||||
// This is the end of the Sequence
|
||||
AppendBlock(insert);
|
||||
} else {
|
||||
// This is somewhere in the middle of the Sequence
|
||||
InsertBlockBetweenBlocks(insert, trim_out_to_in, trim_in_to_out);
|
||||
}
|
||||
}
|
||||
|
||||
UnblockInvalidateCache();
|
||||
|
||||
InvalidateCache(in, out);
|
||||
}
|
||||
|
||||
void TrackOutput::ReplaceBlock(Block *old, Block *replace)
|
||||
{
|
||||
Q_ASSERT(old->length() == replace->length());
|
||||
|
||||
@@ -41,8 +41,6 @@ public:
|
||||
virtual QString Category() override;
|
||||
virtual QString Description() override;
|
||||
|
||||
virtual void set_length(const rational &length) override;
|
||||
|
||||
virtual void Refresh() override;
|
||||
|
||||
const int& Index();
|
||||
@@ -104,15 +102,6 @@ public:
|
||||
*/
|
||||
void AppendBlock(Block* block);
|
||||
|
||||
/**
|
||||
* @brief Destructively places `block` at the in point `start`
|
||||
*
|
||||
* The Block is guaranteed to be placed at the starting point specified. If there are Blocks in this area, they are
|
||||
* either trimmed or removed to make space for this Block. Additionally, if the Block is placed beyond the end of
|
||||
* the Sequence, a GapBlock is inserted to compensate.
|
||||
*/
|
||||
void PlaceBlock(Block* block, rational start);
|
||||
|
||||
/**
|
||||
* @brief Removes a Block and places a Gap in its place
|
||||
*/
|
||||
@@ -123,31 +112,6 @@ public:
|
||||
*/
|
||||
void RippleRemoveBlock(Block* block);
|
||||
|
||||
/**
|
||||
* @brief Splits `block` into two Blocks at the Sequence point `time`
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* The second block created as a result of this split
|
||||
*/
|
||||
Block *SplitBlock(Block* block, rational time);
|
||||
|
||||
/**
|
||||
* @brief Attempt to split at a certain time
|
||||
*
|
||||
* Finds the Block that surrounds this time and splits it. If there is no Block there, this is a no-op.
|
||||
*/
|
||||
void SplitAtTime(rational time);
|
||||
|
||||
/**
|
||||
* @brief Clears the area between in and out
|
||||
*
|
||||
* The area between `in` and `out` is guaranteed to be freed. BLocks are trimmed and removed to free this space.
|
||||
* By default, nothing takes this area meaning all subsequent clips are pushed backward, however you can specify
|
||||
* a block to insert at the `in` point. No checking is done to ensure `insert` is the same length as `in` to `out`.
|
||||
*/
|
||||
void RippleRemoveArea(rational in, rational out, Block* insert = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Replaces Block `old` with Block `replace`
|
||||
*
|
||||
@@ -155,6 +119,17 @@ public:
|
||||
*/
|
||||
void ReplaceBlock(Block* old, Block* replace);
|
||||
|
||||
void BlockInvalidateCache();
|
||||
|
||||
void UnblockInvalidateCache();
|
||||
|
||||
/**
|
||||
* @brief Adds a Block to the parent graph so it can be connected to other Nodes
|
||||
*
|
||||
* Also runs through Node's dependencies (the Nodes whose outputs are connected to this Node's inputs)
|
||||
*/
|
||||
void AddBlockToGraph(Block* block);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Signal emitted when a Block is added to this Track
|
||||
@@ -180,21 +155,11 @@ private:
|
||||
*/
|
||||
void RemoveBlockInternal();
|
||||
|
||||
/**
|
||||
* @brief Adds a Block to the parent graph so it can be connected to other Nodes
|
||||
*
|
||||
* Also runs through Node's dependencies (the Nodes whose outputs are connected to this Node's inputs)
|
||||
*/
|
||||
void AddBlockToGraph(Block* block);
|
||||
|
||||
/**
|
||||
* @brief Sets current_block_ to the correct attached Block based on `time`
|
||||
*/
|
||||
void ValidateCurrentBlock(const rational& time);
|
||||
|
||||
void BlockInvalidateCache();
|
||||
void UnblockInvalidateCache();
|
||||
|
||||
QVector<Block*> block_cache_;
|
||||
|
||||
Block* current_block_;
|
||||
|
||||
@@ -78,6 +78,13 @@ const QVector<NodeEdgePtr> &NodeParam::edges()
|
||||
return edges_;
|
||||
}
|
||||
|
||||
void NodeParam::DisconnectAll()
|
||||
{
|
||||
while (!edges_.isEmpty()) {
|
||||
DisconnectEdge(edges_.first());
|
||||
}
|
||||
}
|
||||
|
||||
bool NodeParam::AreDataTypesCompatible(NodeParam *a, NodeParam *b)
|
||||
{
|
||||
// Make sure one is an input and one is an output
|
||||
@@ -159,6 +166,10 @@ NodeEdgePtr NodeParam::ConnectEdge(NodeOutput *output, NodeInput *input)
|
||||
|
||||
NodeEdgePtr edge = std::make_shared<NodeEdge>(output, input);
|
||||
|
||||
// The nodes should never be the same, and since we lock both nodes here, this can lead to a entire program freeze
|
||||
// that's difficult to diagnose. This makes that issue very clear.
|
||||
Q_ASSERT(output->parent() != input->parent());
|
||||
|
||||
output->parent()->Lock();
|
||||
input->parent()->Lock();
|
||||
|
||||
|
||||
@@ -157,6 +157,11 @@ public:
|
||||
*/
|
||||
const QVector<NodeEdgePtr>& edges();
|
||||
|
||||
/**
|
||||
* @brief Disconnect any edges connecting this parameter to other parameters
|
||||
*/
|
||||
void DisconnectAll();
|
||||
|
||||
/**
|
||||
* @brief Determine whether two DataTypes are compatible and therefore whether two NodeParams can be connected
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user