Files
oak-editor/app/node/output/track/track.cpp
T
itsmattkc 9f89f57090 track: when length changes, force the new area to invalidate
The track limits cache invalidation signals to the length of the track. On some
platforms, if a block length was changed to the point that it changed the
track length, the track length would change AFTER the cache invalidation and
therefore never end up getting rendered.

This commit ensures that when a track's length changes, the new section is
invalidated regardless of ordering.
2020-04-07 02:34:59 +10:00

572 lines
12 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "track.h"
#include <QApplication>
#include <QDebug>
#include <QFontMetrics>
#include "node/block/gap/gap.h"
#include "node/graph.h"
OLIVE_NAMESPACE_ENTER
TrackOutput::TrackOutput() :
track_type_(Timeline::kTrackTypeNone),
block_invalidate_cache_stack_(0),
index_(-1),
locked_(false)
{
block_input_ = new NodeInputArray("block_in", NodeParam::kAny);
block_input_->set_is_keyframable(false);
AddInput(block_input_);
connect(block_input_, &NodeInputArray::EdgeAdded, this, &TrackOutput::BlockConnected);
connect(block_input_, &NodeInputArray::EdgeRemoved, this, &TrackOutput::BlockDisconnected);
connect(block_input_, &NodeInputArray::SizeChanged, this, &TrackOutput::BlockListSizeChanged);
muted_input_ = new NodeInput("muted_in", NodeParam::kBoolean);
muted_input_->set_is_keyframable(false);
AddInput(muted_input_);
// Set default height
track_height_ = GetDefaultTrackHeight();
}
void TrackOutput::set_track_type(const Timeline::TrackType &track_type)
{
track_type_ = track_type;
}
const Timeline::TrackType& TrackOutput::track_type()
{
return track_type_;
}
Block::Type TrackOutput::type() const
{
return kClip;
}
Block *TrackOutput::copy() const
{
return new TrackOutput();
}
QString TrackOutput::Name() const
{
return tr("Track");
}
QString TrackOutput::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.track");
}
QString TrackOutput::Category() const
{
return tr("Output");
}
QString TrackOutput::Description() const
{
return tr("Node for representing and processing a single array of Blocks sorted by time. Also represents the end of "
"a Sequence.");
}
QString TrackOutput::GetTrackName()
{
if (track_name_.isEmpty()) {
return GetDefaultTrackName(track_type_, index_);
}
return track_name_;
}
const int &TrackOutput::GetTrackHeight() const
{
return track_height_;
}
void TrackOutput::SetTrackHeight(const int &height)
{
track_height_ = height;
emit TrackHeightChanged(track_height_);
}
void TrackOutput::Retranslate()
{
block_input_->set_name(tr("Blocks"));
muted_input_->set_name(tr("Muted"));
}
const int &TrackOutput::Index()
{
return index_;
}
void TrackOutput::SetIndex(const int &index)
{
index_ = index;
}
Block *TrackOutput::BlockContainingTime(const rational &time) const
{
foreach (Block* block, block_cache_) {
if (block->in() < time && block->out() > time) {
return block;
} else if (block->out() == time) {
break;
}
}
return nullptr;
}
Block *TrackOutput::NearestBlockBefore(const rational &time) const
{
foreach (Block* block, block_cache_) {
// Blocks are sorted by time, so the first Block who's out point is at/after this time is the correct Block
if (block->out() >= time) {
return block;
}
}
return nullptr;
}
Block *TrackOutput::NearestBlockBeforeOrAt(const rational &time) const
{
foreach (Block* block, block_cache_) {
// Blocks are sorted by time, so the first Block who's out point is at/after this time is the correct Block
if (block->out() > time) {
return block;
}
}
return nullptr;
}
Block *TrackOutput::NearestBlockAfterOrAt(const rational &time) const
{
foreach (Block* block, block_cache_) {
// Blocks are sorted by time, so the first Block after this time is the correct Block
if (block->in() >= time) {
return block;
}
}
return nullptr;
}
Block *TrackOutput::NearestBlockAfter(const rational &time) const
{
foreach (Block* block, block_cache_) {
// Blocks are sorted by time, so the first Block after this time is the correct Block
if (block->in() > time) {
return block;
}
}
return nullptr;
}
Block *TrackOutput::BlockAtTime(const rational &time) const
{
if (IsMuted()) {
return nullptr;
}
foreach (Block* block, block_cache_) {
if (block
&& block->in() <= time
&& block->out() > time) {
return block;
}
}
return nullptr;
}
QList<Block *> TrackOutput::BlocksAtTimeRange(const TimeRange &range) const
{
QList<Block*> list;
if (IsMuted()) {
return list;
}
foreach (Block* block, block_cache_) {
if (block
&& block->out() > range.in()
&& block->in() < range.out()) {
list.append(block);
}
}
return list;
}
const QVector<Block *> &TrackOutput::Blocks() const
{
return block_cache_;
}
void TrackOutput::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from)
{
if (block_invalidate_cache_stack_ == 0) {
Node::InvalidateCache(qMax(start_range, rational(0)), qMin(end_range, track_length()), from);
}
}
void TrackOutput::InsertBlockBefore(Block* block, Block* after)
{
InsertBlockAtIndex(block, block_cache_.indexOf(after));
}
void TrackOutput::InsertBlockAfter(Block *block, Block *before)
{
int before_index = block_cache_.indexOf(before);
Q_ASSERT(before_index >= 0);
if (before_index == block_cache_.size() - 1) {
AppendBlock(block);
} else {
InsertBlockAtIndex(block, before_index + 1);
}
}
void TrackOutput::PrependBlock(Block *block)
{
InsertBlockAtIndex(block, 0);
}
void TrackOutput::InsertBlockAtIndex(Block *block, int index)
{
BlockInvalidateCache();
block_input_->InsertAt(index);
NodeParam::ConnectEdge(block->output(),
block_input_->At(index));
UnblockInvalidateCache();
InvalidateCache(block->in(), track_length());
}
void TrackOutput::AppendBlock(Block *block)
{
BlockInvalidateCache();
int last_index = block_input_->GetSize();
block_input_->Append();
NodeParam::ConnectEdge(block->output(),
block_input_->At(last_index));
UnblockInvalidateCache();
// Invalidate area that block was added to
InvalidateCache(block->in(), track_length());
}
void TrackOutput::BlockInvalidateCache()
{
block_invalidate_cache_stack_++;
}
void TrackOutput::UnblockInvalidateCache()
{
block_invalidate_cache_stack_--;
}
void TrackOutput::RippleRemoveBlock(Block *block)
{
BlockInvalidateCache();
rational remove_in = block->in();
int index_of_block_to_remove = block_cache_.indexOf(block);
block_input_->RemoveAt(index_of_block_to_remove);
UnblockInvalidateCache();
InvalidateCache(remove_in, track_length());
}
void TrackOutput::ReplaceBlock(Block *old, Block *replace)
{
BlockInvalidateCache();
int index_of_old_block = block_cache_.indexOf(old);
NodeParam::DisconnectEdge(old->output(),
block_input_->At(index_of_old_block));
NodeParam::ConnectEdge(replace->output(),
block_input_->At(index_of_old_block));
UnblockInvalidateCache();
if (old->length() == replace->length()) {
InvalidateCache(replace->in(), replace->out());
} else {
InvalidateCache(replace->in(), RATIONAL_MAX);
}
}
TrackOutput *TrackOutput::TrackFromBlock(const Block *block)
{
NodeOutput* output = block->output();
foreach (NodeEdgePtr edge, output->edges()) {
Node* n = edge->input()->parentNode();
if (n->IsTrack()) {
return static_cast<TrackOutput*>(n);
}
}
return nullptr;
}
const rational &TrackOutput::track_length() const
{
return track_length_;
}
bool TrackOutput::IsTrack() const
{
return true;
}
int TrackOutput::GetTrackHeightIncrement()
{
return qApp->fontMetrics().height() / 2;
}
int TrackOutput::GetDefaultTrackHeight()
{
return qApp->fontMetrics().height() * 3;
}
int TrackOutput::GetTrackHeightMinimum()
{
return qApp->fontMetrics().height() * 3 / 2;
}
QString TrackOutput::GetDefaultTrackName(Timeline::TrackType type, int index)
{
// Starts tracks at 1 rather than 0
int user_friendly_index = index+1;
switch (type) {
case Timeline::kTrackTypeVideo: return tr("Video %1").arg(user_friendly_index);
case Timeline::kTrackTypeAudio: return tr("Audio %1").arg(user_friendly_index);
case Timeline::kTrackTypeSubtitle: return tr("Subtitle %1").arg(user_friendly_index);
case Timeline::kTrackTypeNone:
case Timeline::kTrackTypeCount:
break;
}
return tr("Track %1").arg(user_friendly_index);
}
bool TrackOutput::IsMuted() const
{
return muted_input_->get_standard_value().toBool();
}
bool TrackOutput::IsLocked() const
{
return locked_;
}
void TrackOutput::SetTrackName(const QString &name)
{
track_name_ = name;
}
void TrackOutput::SetMuted(bool e)
{
muted_input_->set_standard_value(e);
InvalidateCache(0, track_length());
}
void TrackOutput::SetLocked(bool e)
{
locked_ = e;
}
void TrackOutput::UpdateInOutFrom(int index)
{
Q_ASSERT(index >= 0);
Q_ASSERT(index < block_cache_.size());
rational new_track_length;
// Find block just before this one to find the last out point
for (int i=index-1;i>=0;i--) {
Block* b = block_cache_.at(i);
if (b) {
new_track_length = b->out();
break;
}
}
for (int i=index;i<block_cache_.size();i++) {
Block* b = block_cache_.at(i);
if (b) {
// Set in
b->set_in(new_track_length);
// Set out
new_track_length += b->length();
b->set_out(new_track_length);
emit b->Refreshed();
}
}
// Update track length
if (new_track_length != track_length_) {
rational old_track_length = track_length_;
track_length_ = new_track_length;
emit TrackLengthChanged();
InvalidateCache(qMin(old_track_length, new_track_length),
qMax(old_track_length, new_track_length));
}
}
void TrackOutput::UpdatePreviousAndNextOfIndex(int index)
{
Block* ref = block_cache_.at(index);
Block* previous = nullptr;
Block* next = nullptr;
// Find previous
for (int i=index-1;i>=0;i--) {
previous = block_cache_.at(i);
if (previous) {
break;
}
}
// Find next
for (int i=index+1;i<block_cache_.size();i++) {
next = block_cache_.at(i);
if (next) {
break;
}
}
if (ref) {
// Link blocks together
ref->set_previous(previous);
ref->set_next(next);
if (previous)
previous->set_next(ref);
if (next)
next->set_previous(ref);
} else {
// Link previous and next together
if (previous)
previous->set_next(next);
if (next)
next->set_previous(previous);
}
}
void TrackOutput::BlockConnected(NodeEdgePtr edge)
{
int block_index = block_input_->IndexOfSubParameter(edge->input());
Q_ASSERT(block_index >= 0);
Node* connected_node = edge->output()->parentNode();
Block* connected_block = connected_node->IsBlock() ? static_cast<Block*>(connected_node) : nullptr;
block_cache_.replace(block_index, connected_block);
UpdatePreviousAndNextOfIndex(block_index);
UpdateInOutFrom(block_index);
if (connected_block) {
connect(connected_block, SIGNAL(LengthChanged(const rational&)), this, SLOT(BlockLengthChanged()));
emit BlockAdded(connected_block);
}
}
void TrackOutput::BlockDisconnected(NodeEdgePtr edge)
{
int block_index = block_input_->IndexOfSubParameter(edge->input());
Q_ASSERT(block_index >= 0);
block_cache_.replace(block_index, nullptr);
UpdatePreviousAndNextOfIndex(block_index);
UpdateInOutFrom(block_index);
Node* connected_node = edge->output()->parentNode();
Block* connected_block = connected_node->IsBlock() ? static_cast<Block*>(connected_node) : nullptr;
if (connected_block) {
disconnect(connected_block, SIGNAL(LengthChanged(const rational&)), this, SLOT(BlockLengthChanged()));
// Update previous and next references
emit BlockRemoved(connected_block);
}
}
void TrackOutput::BlockListSizeChanged(int size)
{
int old_size = block_cache_.size();
block_cache_.resize(size);
// Fill new slots with nullptr
for (int i=old_size;i<size;i++) {
block_cache_.replace(i, nullptr);
}
}
void TrackOutput::BlockLengthChanged()
{
// Assumes sender is a Block
Block* b = static_cast<Block*>(sender());
int index = block_cache_.indexOf(b);
Q_ASSERT(index >= 0);
UpdateInOutFrom(index);
}
OLIVE_NAMESPACE_EXIT