work towards making timeline actions all undoable

This commit is contained in:
itsmattkc
2019-09-23 23:00:15 +10:00
parent d0d4756129
commit 9fa22938b9
31 changed files with 994 additions and 378 deletions
+13
View File
@@ -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()
+3 -2
View File
@@ -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
View File
@@ -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);
}
+9
View File
@@ -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
+5
View File
@@ -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
*/
+18 -80
View File
@@ -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);
}
}
}
}
+6 -27
View File
@@ -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_;
-160
View File
@@ -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());
+11 -46
View File
@@ -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_;
+11
View File
@@ -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();
+5
View File
@@ -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
*