work towards making timeline actions all undoable
This commit is contained in:
@@ -28,7 +28,8 @@ QString padded(int arg, int padding) {
|
||||
|
||||
QString olive::timestamp_to_timecode(const int64_t ×tamp,
|
||||
const rational& timebase,
|
||||
const TimecodeDisplay& display)
|
||||
const TimecodeDisplay& display,
|
||||
bool show_plus_if_positive)
|
||||
{
|
||||
double timestamp_dbl = (rational(timestamp) * timebase).toDouble();
|
||||
|
||||
@@ -36,6 +37,16 @@ QString olive::timestamp_to_timecode(const int64_t ×tamp,
|
||||
case kTimecodeFrames:
|
||||
case kTimecodeSeconds:
|
||||
{
|
||||
QString prefix;
|
||||
|
||||
if (timestamp_dbl < 0) {
|
||||
prefix = "-";
|
||||
} else if (show_plus_if_positive) {
|
||||
prefix = "+";
|
||||
}
|
||||
|
||||
timestamp_dbl = qAbs(timestamp_dbl);
|
||||
|
||||
int total_seconds = qFloor(timestamp_dbl);
|
||||
|
||||
int hours = total_seconds / 3600;
|
||||
@@ -45,19 +56,21 @@ QString olive::timestamp_to_timecode(const int64_t ×tamp,
|
||||
if (display == kTimecodeSeconds) {
|
||||
int fraction = qRound((timestamp_dbl - total_seconds) * 1000);
|
||||
|
||||
return QString("%1:%2:%3.%4").arg(padded(hours, 2),
|
||||
padded(mins, 2),
|
||||
padded(secs, 2),
|
||||
padded(fraction, 3));
|
||||
return QString("%1%2:%3:%4.%5").arg(prefix,
|
||||
padded(hours, 2),
|
||||
padded(mins, 2),
|
||||
padded(secs, 2),
|
||||
padded(fraction, 3));
|
||||
} else {
|
||||
rational frame_rate = timebase.flipped();
|
||||
|
||||
int frames = qRound((timestamp_dbl - total_seconds) * frame_rate.toDouble());
|
||||
|
||||
return QString("%1:%2:%3;%4").arg(padded(hours, 2),
|
||||
padded(mins, 2),
|
||||
padded(secs, 2),
|
||||
padded(frames, 2));
|
||||
return QString("%1%2:%3:%4;%5").arg(prefix,
|
||||
padded(hours, 2),
|
||||
padded(mins, 2),
|
||||
padded(secs, 2),
|
||||
padded(frames, 2));
|
||||
}
|
||||
}
|
||||
case kFrames:
|
||||
@@ -68,3 +81,13 @@ QString olive::timestamp_to_timecode(const int64_t ×tamp,
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
rational olive::timestamp_to_time(const int64_t ×tamp, const rational &timebase)
|
||||
{
|
||||
return rational(timestamp) * timebase;
|
||||
}
|
||||
|
||||
int64_t olive::time_to_timestamp(const rational &time, const rational &timebase)
|
||||
{
|
||||
return qRound64(time.toDouble() * timebase.flipped().toDouble());
|
||||
}
|
||||
|
||||
@@ -37,7 +37,11 @@ enum TimecodeDisplay {
|
||||
/**
|
||||
* @brief Convert a timestamp (according to a rational timebase) to a user-friendly string representation
|
||||
*/
|
||||
QString timestamp_to_timecode(const int64_t ×tamp, const rational& timebase, const TimecodeDisplay& display);
|
||||
QString timestamp_to_timecode(const int64_t ×tamp, const rational& timebase, const TimecodeDisplay& display, bool show_plus_if_positive = false);
|
||||
|
||||
int64_t time_to_timestamp(const rational& time, const rational& timebase);
|
||||
|
||||
rational timestamp_to_time(const int64_t& timestamp, const rational& timebase);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ extern "C" {
|
||||
#include <QtMath>
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "render/pixelservice.h"
|
||||
|
||||
FFmpegDecoder::FFmpegDecoder() :
|
||||
@@ -326,7 +327,7 @@ int64_t FFmpegDecoder::GetTimestampFromTime(const rational &time)
|
||||
}
|
||||
|
||||
// Convert timecode to AVStream timebase
|
||||
int64_t target_ts = qRound64(time.toDouble() * rational(avstream_->time_base).flipped().toDouble());
|
||||
int64_t target_ts = olive::time_to_timestamp(time, avstream_->time_base);
|
||||
|
||||
// Find closest actual timebase in the file
|
||||
target_ts = GetClosestTimestampInIndex(target_ts);
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -340,11 +340,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action
|
||||
}
|
||||
}
|
||||
|
||||
if (move_command->childCount() > 0) {
|
||||
olive::undo_stack.push(move_command);
|
||||
} else {
|
||||
delete move_command;
|
||||
}
|
||||
olive::undo_stack.pushIfHasChildren(move_command);
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
+10
-1
@@ -1,3 +1,12 @@
|
||||
#include "undostack.h"
|
||||
|
||||
QUndoStack olive::undo_stack;
|
||||
OliveUndoStack olive::undo_stack;
|
||||
|
||||
void OliveUndoStack::pushIfHasChildren(QUndoCommand *command)
|
||||
{
|
||||
if (command->childCount() > 0) {
|
||||
push(command);
|
||||
} else {
|
||||
delete command;
|
||||
}
|
||||
}
|
||||
|
||||
+11
-1
@@ -3,11 +3,21 @@
|
||||
|
||||
#include <QUndoStack>
|
||||
|
||||
class OliveUndoStack : public QUndoStack {
|
||||
public:
|
||||
/**
|
||||
* @brief A wrapper for push() that either pushes if the command has children or deletes if not
|
||||
*
|
||||
* This function takes ownership of `command`, and may delete it so it should never be accessed after this call.
|
||||
*/
|
||||
void pushIfHasChildren(QUndoCommand* command);
|
||||
};
|
||||
|
||||
namespace olive {
|
||||
/**
|
||||
* @brief A static undo stack for undoable commands throughout Olive
|
||||
*/
|
||||
extern QUndoStack undo_stack;
|
||||
extern OliveUndoStack undo_stack;
|
||||
}
|
||||
|
||||
#endif // UNDOSTACK_H
|
||||
|
||||
@@ -458,11 +458,7 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
|
||||
dragging_edge_ = nullptr;
|
||||
|
||||
if (node_edge_change_command_->childCount() > 0) {
|
||||
olive::undo_stack.push(node_edge_change_command_);
|
||||
} else {
|
||||
delete node_edge_change_command_;
|
||||
}
|
||||
olive::undo_stack.pushIfHasChildren(node_edge_change_command_);
|
||||
node_edge_change_command_ = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(tool)
|
||||
add_subdirectory(undo)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
|
||||
@@ -37,6 +37,7 @@ TimelineView::TimelineView(QWidget *parent) :
|
||||
pointer_tool_(this),
|
||||
import_tool_(this),
|
||||
ripple_tool_(this),
|
||||
rolling_tool_(this),
|
||||
razor_tool_(this),
|
||||
hand_tool_(this),
|
||||
zoom_tool_(this),
|
||||
@@ -65,20 +66,20 @@ void TimelineView::AddBlock(Block *block, int track)
|
||||
case Block::kClip:
|
||||
case Block::kGap:
|
||||
{
|
||||
TimelineViewBlockItem* clip_item = new TimelineViewBlockItem();
|
||||
TimelineViewBlockItem* item = new TimelineViewBlockItem();
|
||||
|
||||
// Set up clip with view parameters (clip item will automatically size its rect accordingly)
|
||||
clip_item->SetBlock(block);
|
||||
clip_item->SetY(GetTrackY(track));
|
||||
clip_item->SetHeight(GetTrackHeight(track));
|
||||
clip_item->SetScale(scale_);
|
||||
clip_item->SetTrack(track);
|
||||
item->SetBlock(block);
|
||||
item->SetY(GetTrackY(track));
|
||||
item->SetHeight(GetTrackHeight(track));
|
||||
item->SetScale(scale_);
|
||||
item->SetTrack(track);
|
||||
|
||||
// Add to list of clip items that can be iterated through
|
||||
clip_items_.insert(block, clip_item);
|
||||
block_items_.insert(block, item);
|
||||
|
||||
// Add item to graphics scene
|
||||
scene_.addItem(clip_item);
|
||||
scene_.addItem(item);
|
||||
|
||||
connect(block, SIGNAL(Refreshed()), this, SLOT(BlockChanged()));
|
||||
break;
|
||||
@@ -93,9 +94,9 @@ void TimelineView::AddBlock(Block *block, int track)
|
||||
|
||||
void TimelineView::RemoveBlock(Block *block)
|
||||
{
|
||||
delete clip_items_[block];
|
||||
delete block_items_[block];
|
||||
|
||||
clip_items_.remove(block);
|
||||
block_items_.remove(block);
|
||||
}
|
||||
|
||||
void TimelineView::AddTrack(TrackOutput *track)
|
||||
@@ -116,7 +117,7 @@ void TimelineView::SetScale(const double &scale)
|
||||
{
|
||||
scale_ = scale;
|
||||
|
||||
QMapIterator<Block*, TimelineViewRect*> iterator(clip_items_);
|
||||
QMapIterator<Block*, TimelineViewRect*> iterator(block_items_);
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
iterator.next();
|
||||
@@ -143,7 +144,7 @@ void TimelineView::SetTimebase(const rational &timebase)
|
||||
|
||||
void TimelineView::Clear()
|
||||
{
|
||||
QMapIterator<Block*, TimelineViewRect*> iterator(clip_items_);
|
||||
QMapIterator<Block*, TimelineViewRect*> iterator(block_items_);
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
iterator.next();
|
||||
@@ -153,7 +154,7 @@ void TimelineView::Clear()
|
||||
}
|
||||
}
|
||||
|
||||
clip_items_.clear();
|
||||
block_items_.clear();
|
||||
}
|
||||
|
||||
void TimelineView::ConnectTimelineNode(TimelineOutput *node)
|
||||
@@ -265,7 +266,7 @@ TimelineView::Tool *TimelineView::GetActiveTool()
|
||||
case olive::tool::kRipple:
|
||||
return &ripple_tool_;
|
||||
case olive::tool::kRolling:
|
||||
return nullptr; // FIXME: Implement
|
||||
return &rolling_tool_;
|
||||
case olive::tool::kRazor:
|
||||
return &razor_tool_;
|
||||
case olive::tool::kSlip:
|
||||
@@ -353,7 +354,7 @@ void TimelineView::ClearGhosts()
|
||||
|
||||
void TimelineView::BlockChanged()
|
||||
{
|
||||
TimelineViewRect* rect = clip_items_[static_cast<Block*>(sender())];
|
||||
TimelineViewRect* rect = block_items_[static_cast<Block*>(sender())];
|
||||
|
||||
if (rect != nullptr) {
|
||||
rect->UpdateRect();
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#include "timelineviewblockitem.h"
|
||||
#include "timelineviewghostitem.h"
|
||||
#include "timelineviewplayheaditem.h"
|
||||
#include "widget/timelineview/undo/undo.h"
|
||||
#include "undo/undostack.h"
|
||||
|
||||
/**
|
||||
* @brief A widget for viewing and interacting Sequences
|
||||
@@ -230,6 +232,9 @@ private:
|
||||
virtual void MousePress(QMouseEvent *event);
|
||||
virtual void MouseMove(QMouseEvent *event);
|
||||
virtual void MouseRelease(QMouseEvent *event);
|
||||
|
||||
private:
|
||||
QVector<int> split_tracks_;
|
||||
};
|
||||
|
||||
class RippleTool : public PointerTool
|
||||
@@ -245,6 +250,20 @@ private:
|
||||
bool allow_gap_trimming) override;
|
||||
};
|
||||
|
||||
class RollingTool : public PointerTool
|
||||
{
|
||||
public:
|
||||
RollingTool(TimelineView* parent);
|
||||
|
||||
protected:
|
||||
virtual void MouseReleaseInternal(QMouseEvent *event) override;
|
||||
virtual rational FrameValidateInternal(rational time_movement, const QVector<TimelineViewGhostItem*>& ghosts) override;
|
||||
|
||||
virtual void InitiateGhosts(TimelineViewBlockItem* clicked_item,
|
||||
olive::timeline::MovementMode trim_mode,
|
||||
bool allow_gap_trimming) override;
|
||||
};
|
||||
|
||||
class HandTool : public Tool
|
||||
{
|
||||
public:
|
||||
@@ -277,6 +296,7 @@ private:
|
||||
PointerTool pointer_tool_;
|
||||
ImportTool import_tool_;
|
||||
RippleTool ripple_tool_;
|
||||
RollingTool rolling_tool_;
|
||||
RazorTool razor_tool_;
|
||||
HandTool hand_tool_;
|
||||
ZoomTool zoom_tool_;
|
||||
@@ -302,7 +322,7 @@ private:
|
||||
|
||||
int64_t playhead_;
|
||||
|
||||
QMap<Block*, TimelineViewRect*> clip_items_;
|
||||
QMap<Block*, TimelineViewRect*> block_items_;
|
||||
|
||||
QVector<TimelineViewGhostItem*> ghost_items_;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ set(OLIVE_SOURCES
|
||||
widget/timelineview/tool/pointer.cpp
|
||||
widget/timelineview/tool/razor.cpp
|
||||
widget/timelineview/tool/ripple.cpp
|
||||
widget/timelineview/tool/rolling.cpp
|
||||
widget/timelineview/tool/tool.cpp
|
||||
widget/timelineview/tool/zoom.cpp
|
||||
PARENT_SCOPE
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "widget/timelineview/timelineview.h"
|
||||
|
||||
#include <QMimeData>
|
||||
#include <QToolTip>
|
||||
|
||||
#include "config/config.h"
|
||||
#include "common/qtversionabstraction.h"
|
||||
@@ -131,6 +132,8 @@ void TimelineView::ImportTool::DragMove(QDragMoveEvent *event)
|
||||
SnapPoint(snap_points_, &time_movement);
|
||||
}
|
||||
|
||||
rational earliest_ghost = RATIONAL_MAX;
|
||||
|
||||
// Move ghosts to the mouse cursor
|
||||
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
|
||||
ghost->SetInAdjustment(time_movement);
|
||||
@@ -140,8 +143,19 @@ void TimelineView::ImportTool::DragMove(QDragMoveEvent *event)
|
||||
|
||||
ghost->SetY(ghost_y);
|
||||
ghost->SetHeight(ghost_height);
|
||||
|
||||
earliest_ghost = qMin(earliest_ghost, ghost->GetAdjustedIn());
|
||||
}
|
||||
|
||||
// Generate tooltip (showing earliest in point of imported clip)
|
||||
int64_t earliest_timestamp = olive::time_to_timestamp(earliest_ghost, parent()->timebase_);
|
||||
QString tooltip_text = olive::timestamp_to_timecode(earliest_timestamp,
|
||||
parent()->timebase_,
|
||||
kTimecodeDisplay);
|
||||
QToolTip::showText(QCursor::pos(),
|
||||
tooltip_text,
|
||||
parent());
|
||||
|
||||
event->accept();
|
||||
} else {
|
||||
event->ignore();
|
||||
@@ -166,6 +180,8 @@ void TimelineView::ImportTool::DragDrop(QDropEvent *event)
|
||||
// of scope will delete the nodes. If there is, they'll become parents of the NodeGraph instead
|
||||
QObject node_memory_manager;
|
||||
|
||||
QUndoCommand* command = new QUndoCommand();
|
||||
|
||||
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
|
||||
ClipBlock* clip = new ClipBlock();
|
||||
MediaInput* media = new MediaInput();
|
||||
@@ -187,13 +203,16 @@ void TimelineView::ImportTool::DragDrop(QDropEvent *event)
|
||||
NodeParam::ConnectEdge(media->texture_output(), opacity->texture_input());
|
||||
NodeParam::ConnectEdge(transform->matrix_output(), media->matrix_input());
|
||||
|
||||
|
||||
if (event->keyboardModifiers() & Qt::ControlModifier) {
|
||||
//emit parent()->RequestInsertBlockAtTime(clip, ghost->GetAdjustedIn());
|
||||
} else {
|
||||
parent()->timeline_node_->PlaceBlock(clip, ghost->GetAdjustedIn(), ghost->Track());
|
||||
new TrackPlaceBlockCommand(parent()->timeline_node_, ghost->Track(), clip, ghost->GetAdjustedIn(), command);
|
||||
}
|
||||
}
|
||||
|
||||
olive::undo_stack.pushIfHasChildren(command);
|
||||
|
||||
parent()->ClearGhosts();
|
||||
|
||||
event->accept();
|
||||
|
||||
@@ -21,9 +21,12 @@
|
||||
#include "widget/timelineview/timelineview.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QToolTip>
|
||||
|
||||
#include "common/clamp.h"
|
||||
#include "common/range.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
#include "node/block/gap/gap.h"
|
||||
|
||||
@@ -112,6 +115,8 @@ void TimelineView::PointerTool::MouseReleaseInternal(QMouseEvent *event)
|
||||
// get cleaned up if they aren't re-parented by the attached NodeGraph
|
||||
QObject block_memory_manager;
|
||||
|
||||
QUndoCommand* command = new QUndoCommand();
|
||||
|
||||
// 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
|
||||
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
|
||||
@@ -122,7 +127,7 @@ void TimelineView::PointerTool::MouseReleaseInternal(QMouseEvent *event)
|
||||
gap->setParent(&block_memory_manager);
|
||||
gap->set_length(b->length());
|
||||
|
||||
parent()->timeline_node_->ReplaceBlock(b, gap, ghost->Track());
|
||||
new TrackReplaceBlockCommand(parent()->timeline_node_->TrackAt(ghost->Track()), b, gap, command);
|
||||
}
|
||||
|
||||
// Now we place the clips back in the timeline where the user moved them. It's legal for them to overwrite parts or
|
||||
@@ -136,14 +141,16 @@ void TimelineView::PointerTool::MouseReleaseInternal(QMouseEvent *event)
|
||||
|
||||
// If we were trimming the in point, we'll need to adjust the media in too
|
||||
if (ghost->mode() == olive::timeline::kTrimIn) {
|
||||
b->set_media_in(b->media_in() + ghost->InAdjustment());
|
||||
new BlockResizeWithMediaInCommand(b, ghost->AdjustedLength(), command);
|
||||
} else {
|
||||
new BlockResizeCommand(b, ghost->AdjustedLength(), command);
|
||||
}
|
||||
|
||||
b->set_length(ghost->AdjustedLength());
|
||||
}
|
||||
|
||||
parent()->timeline_node_->PlaceBlock(b, ghost->GetAdjustedIn(), ghost->GetAdjustedTrack());
|
||||
new TrackPlaceBlockCommand(parent()->timeline_node_, ghost->GetAdjustedTrack(), b, ghost->GetAdjustedIn(), command);
|
||||
}
|
||||
|
||||
olive::undo_stack.pushIfHasChildren(command);
|
||||
}
|
||||
|
||||
rational TimelineView::PointerTool::FrameValidateInternal(rational time_movement, const QVector<TimelineViewGhostItem *>& ghosts)
|
||||
@@ -248,6 +255,17 @@ void TimelineView::PointerTool::ProcessDrag(const QPoint &mouse_pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show tooltip
|
||||
// Generate tooltip (showing earliest in point of imported clip)
|
||||
int64_t earliest_timestamp = olive::time_to_timestamp(time_movement, parent()->timebase_);
|
||||
QString tooltip_text = olive::timestamp_to_timecode(earliest_timestamp,
|
||||
parent()->timebase_,
|
||||
kTimecodeDisplay,
|
||||
true);
|
||||
QToolTip::showText(QCursor::pos(),
|
||||
tooltip_text,
|
||||
parent());
|
||||
}
|
||||
|
||||
void TimelineView::PointerTool::InitiateGhosts(TimelineViewBlockItem* clicked_item,
|
||||
|
||||
@@ -27,6 +27,8 @@ TimelineView::RazorTool::RazorTool(TimelineView* parent) :
|
||||
|
||||
void TimelineView::RazorTool::MousePress(QMouseEvent *event)
|
||||
{
|
||||
split_tracks_.clear();
|
||||
|
||||
MouseMove(event);
|
||||
}
|
||||
|
||||
@@ -39,18 +41,30 @@ void TimelineView::RazorTool::MouseMove(QMouseEvent *event)
|
||||
|
||||
QPointF current_scene_pos = GetScenePos(event->pos());
|
||||
|
||||
// Always split at the same time
|
||||
rational split_time = parent()->SceneToTime(drag_start_.x());
|
||||
|
||||
// Split at the current cursor track
|
||||
int split_track = parent()->SceneToTrack(current_scene_pos.y());
|
||||
|
||||
parent()->timeline_node_->SplitAtTime(split_time, split_track);
|
||||
if (!split_tracks_.contains(split_track)) {
|
||||
split_tracks_.append(split_track);
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineView::RazorTool::MouseRelease(QMouseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
// Always split at the same time
|
||||
rational split_time = parent()->SceneToTime(drag_start_.x());
|
||||
|
||||
QUndoCommand* command = new QUndoCommand();
|
||||
|
||||
foreach (int track, split_tracks_) {
|
||||
new TrackSplitAtTimeCommand(parent()->timeline_node_->TrackAt(track), split_time, command);
|
||||
}
|
||||
|
||||
split_tracks_.clear();
|
||||
|
||||
olive::undo_stack.pushIfHasChildren(command);
|
||||
|
||||
dragging_ = false;
|
||||
}
|
||||
|
||||
@@ -36,15 +36,10 @@ void TimelineView::RippleTool::MouseReleaseInternal(QMouseEvent *event)
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve cursor position difference
|
||||
QPointF scene_pos = GetScenePos(event->pos());
|
||||
QPointF movement = scene_pos - drag_start_;
|
||||
|
||||
// For ripple operations, all ghosts will be moving the same way
|
||||
olive::timeline::MovementMode movement_mode = parent()->ghost_items_.first()->mode();
|
||||
|
||||
// The amount to ripple by
|
||||
rational ripple_length = parent()->SceneToTime(movement.x());
|
||||
QUndoCommand* command = new QUndoCommand();
|
||||
|
||||
// Find earliest point to ripple around
|
||||
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
|
||||
@@ -60,24 +55,28 @@ void TimelineView::RippleTool::MouseReleaseInternal(QMouseEvent *event)
|
||||
|
||||
Block* block_to_append_gap_to = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kReferenceBlock));
|
||||
|
||||
parent()->timeline_node_->Tracks().at(ghost->Track())->InsertBlockAfter(gap,
|
||||
block_to_append_gap_to);
|
||||
new TrackInsertBlockBetweenBlocksCommand(parent()->timeline_node_->Tracks().at(ghost->Track()),
|
||||
gap,
|
||||
block_to_append_gap_to,
|
||||
block_to_append_gap_to->next());
|
||||
}
|
||||
} else {
|
||||
// This was a Block that already existed
|
||||
if (ghost->AdjustedLength() > 0) {
|
||||
b->set_length(ghost->AdjustedLength());
|
||||
|
||||
if (movement_mode == olive::timeline::kTrimIn) {
|
||||
// We'll need to shift the media in point too
|
||||
b->set_media_in(b->media_in() + ghost->InAdjustment());
|
||||
new BlockResizeWithMediaInCommand(b, ghost->AdjustedLength(), command);
|
||||
} else {
|
||||
new BlockResizeCommand(b, ghost->AdjustedLength(), command);
|
||||
}
|
||||
} else {
|
||||
// Assumed the Block was a Gap and it was reduced to zero length, remove it here
|
||||
parent()->timeline_node_->Tracks().at(ghost->Track())->RippleRemoveBlock(b);
|
||||
new TrackRippleRemoveBlockCommand(parent()->timeline_node_->Tracks().at(ghost->Track()), b, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
olive::undo_stack.pushIfHasChildren(command);
|
||||
}
|
||||
|
||||
rational TimelineView::RippleTool::FrameValidateInternal(rational time_movement, const QVector<TimelineViewGhostItem *> &ghosts)
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/***
|
||||
|
||||
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 "widget/timelineview/timelineview.h"
|
||||
|
||||
#include "node/block/gap/gap.h"
|
||||
|
||||
TimelineView::RollingTool::RollingTool(TimelineView* parent) :
|
||||
PointerTool(parent)
|
||||
{
|
||||
SetMovementAllowed(false);
|
||||
}
|
||||
|
||||
void TimelineView::RollingTool::MouseReleaseInternal(QMouseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
if (parent()->ghost_items_.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QUndoCommand* command = new QUndoCommand();
|
||||
|
||||
// Find earliest point to ripple around
|
||||
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
|
||||
Block* b = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
|
||||
|
||||
if (ghost->mode() == olive::timeline::kTrimIn) {
|
||||
if (b->previous() == nullptr) {
|
||||
// We'll need to insert a gap here, so we'll do a Place command instead
|
||||
GapBlock* gap = new GapBlock();
|
||||
gap->set_length(ghost->Length());
|
||||
|
||||
new TrackReplaceBlockCommand(parent()->timeline_node_->TrackAt(ghost->Track()), b, gap, command);
|
||||
}
|
||||
|
||||
new BlockResizeWithMediaInCommand(b, ghost->AdjustedLength(), command);
|
||||
|
||||
if (b->previous() == nullptr) {
|
||||
new TrackPlaceBlockCommand(parent()->timeline_node_, ghost->Track(), b, ghost->GetAdjustedIn(), command);
|
||||
}
|
||||
} else if (ghost->mode() == olive::timeline::kTrimOut) {
|
||||
new BlockResizeCommand(b, ghost->AdjustedLength(), command);
|
||||
}
|
||||
}
|
||||
|
||||
olive::undo_stack.pushIfHasChildren(command);
|
||||
}
|
||||
|
||||
rational TimelineView::RollingTool::FrameValidateInternal(rational time_movement, const QVector<TimelineViewGhostItem *> &ghosts)
|
||||
{
|
||||
// Only validate trimming, and we don't care about "overwriting" since the rolling tool is designed to trim at collisions
|
||||
time_movement = ValidateInTrimming(time_movement, ghosts, false);
|
||||
time_movement = ValidateOutTrimming(time_movement, ghosts, false);
|
||||
|
||||
return time_movement;
|
||||
}
|
||||
|
||||
void TimelineView::RollingTool::InitiateGhosts(TimelineViewBlockItem *clicked_item,
|
||||
olive::timeline::MovementMode trim_mode,
|
||||
bool allow_gap_trimming)
|
||||
{
|
||||
Q_UNUSED(allow_gap_trimming)
|
||||
|
||||
PointerTool::InitiateGhosts(clicked_item, trim_mode, true);
|
||||
|
||||
// For each ghost, we make an equivalent Ghost on the next/previous block
|
||||
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
|
||||
Block* ghost_block = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
|
||||
|
||||
if (ghost->mode() == olive::timeline::kTrimIn && ghost_block->previous() != nullptr) {
|
||||
// Add an extra Ghost for the previous block
|
||||
AddGhostFromBlock(ghost_block->previous(), ghost->Track(), olive::timeline::kTrimOut);
|
||||
} else if (ghost->mode() == olive::timeline::kTrimOut && ghost_block->next() != nullptr && ghost_block->next()->type() != Block::kEnd) {
|
||||
AddGhostFromBlock(ghost_block->next(), ghost->Track(), olive::timeline::kTrimIn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# 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/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
widget/timelineview/undo/undo.h
|
||||
widget/timelineview/undo/undo.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,416 @@
|
||||
/***
|
||||
|
||||
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 "undo.h"
|
||||
|
||||
#include "node/graph.h"
|
||||
|
||||
Block* CreateSplitBlock(Block* block, rational point, QObject* parent = nullptr)
|
||||
{
|
||||
Block* copy = block->copy();
|
||||
copy->set_length_and_media_in(block->length() - (point - block->in()));
|
||||
copy->setParent(parent);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
Node* TakeNodeFromParentGraph(Node* n, QObject* new_parent = nullptr) {
|
||||
static_cast<NodeGraph*>(n->parent())->TakeNode(n, new_parent);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
BlockResizeCommand::BlockResizeCommand(Block *block, rational new_length, QUndoCommand* parent) :
|
||||
QUndoCommand(parent),
|
||||
block_(block),
|
||||
old_length_(block->length()),
|
||||
new_length_(new_length)
|
||||
{
|
||||
}
|
||||
|
||||
void BlockResizeCommand::redo()
|
||||
{
|
||||
block_->set_length(new_length_);
|
||||
}
|
||||
|
||||
void BlockResizeCommand::undo()
|
||||
{
|
||||
block_->set_length(old_length_);
|
||||
}
|
||||
|
||||
BlockResizeWithMediaInCommand::BlockResizeWithMediaInCommand(Block *block, rational new_length, QUndoCommand *parent) :
|
||||
QUndoCommand(parent),
|
||||
block_(block),
|
||||
old_length_(block->length()),
|
||||
new_length_(new_length)
|
||||
{
|
||||
}
|
||||
|
||||
void BlockResizeWithMediaInCommand::redo()
|
||||
{
|
||||
block_->set_length_and_media_in(new_length_);
|
||||
}
|
||||
|
||||
void BlockResizeWithMediaInCommand::undo()
|
||||
{
|
||||
block_->set_length_and_media_in(old_length_);
|
||||
}
|
||||
|
||||
BlockSetMediaInCommand::BlockSetMediaInCommand(Block *block, rational new_media_in, QUndoCommand* parent) :
|
||||
QUndoCommand(parent),
|
||||
block_(block),
|
||||
old_media_in_(block->media_in()),
|
||||
new_media_in_(new_media_in)
|
||||
{
|
||||
}
|
||||
|
||||
void BlockSetMediaInCommand::redo()
|
||||
{
|
||||
block_->set_media_in(new_media_in_);
|
||||
}
|
||||
|
||||
void BlockSetMediaInCommand::undo()
|
||||
{
|
||||
block_->set_media_in(old_media_in_);
|
||||
}
|
||||
|
||||
TrackRippleRemoveBlockCommand::TrackRippleRemoveBlockCommand(TrackOutput *track, Block *block, QUndoCommand *parent) :
|
||||
QUndoCommand(parent),
|
||||
track_(track),
|
||||
block_(block),
|
||||
before_(block->previous()),
|
||||
after_(block->next())
|
||||
{
|
||||
}
|
||||
|
||||
void TrackRippleRemoveBlockCommand::redo()
|
||||
{
|
||||
track_->RippleRemoveBlock(block_);
|
||||
}
|
||||
|
||||
void TrackRippleRemoveBlockCommand::undo()
|
||||
{
|
||||
track_->InsertBlockBetweenBlocks(block_, before_, after_);
|
||||
}
|
||||
|
||||
TrackInsertBlockBetweenBlocksCommand::TrackInsertBlockBetweenBlocksCommand(TrackOutput *track,
|
||||
Block *block,
|
||||
Block *before,
|
||||
Block *after,
|
||||
QUndoCommand *parent) :
|
||||
QUndoCommand(parent),
|
||||
track_(track),
|
||||
block_(block),
|
||||
before_(before),
|
||||
after_(after)
|
||||
{
|
||||
}
|
||||
|
||||
void TrackInsertBlockBetweenBlocksCommand::redo()
|
||||
{
|
||||
track_->InsertBlockBetweenBlocks(block_, before_, after_);
|
||||
}
|
||||
|
||||
void TrackInsertBlockBetweenBlocksCommand::undo()
|
||||
{
|
||||
track_->RippleRemoveBlock(block_);
|
||||
}
|
||||
|
||||
TrackRippleRemoveAreaCommand::TrackRippleRemoveAreaCommand(TrackOutput *track, rational in, rational out, QUndoCommand *parent) :
|
||||
QUndoCommand(parent),
|
||||
track_(track),
|
||||
in_(in),
|
||||
out_(out),
|
||||
splice_(nullptr),
|
||||
trim_out_(nullptr),
|
||||
trim_in_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void TrackRippleRemoveAreaCommand::redo()
|
||||
{
|
||||
// Iterate through blocks determining which need trimming/removing/splitting
|
||||
foreach (Block* block, track_->Blocks()) {
|
||||
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
|
||||
removed_blocks_.append(block);
|
||||
} else if (block->in() < in_ && block->out() >= in_) {
|
||||
// This Block's out point exceeds `in`
|
||||
trim_out_ = block;
|
||||
} else if (block->in() <= out_ && block->out() > out_) {
|
||||
// This Block's in point exceeds `out`
|
||||
trim_in_ = block;
|
||||
}
|
||||
}
|
||||
|
||||
// If we picked up a block to splice
|
||||
if (splice_ != nullptr) {
|
||||
|
||||
// Split the block here
|
||||
Block* copy = CreateSplitBlock(splice_, in_);
|
||||
|
||||
// Perform all further actions as if we were just trimming these clips
|
||||
trim_out_ = splice_;
|
||||
trim_in_ = copy;
|
||||
|
||||
}
|
||||
|
||||
// If we picked up a block to trim the in point of
|
||||
if (trim_in_ != nullptr && trim_in_->in() < out_) {
|
||||
trim_in_old_length_ = trim_in_->length();
|
||||
trim_in_new_length_ = trim_in_->out() - out_;
|
||||
}
|
||||
|
||||
// If we picked up a block to trim the out point of
|
||||
if (trim_out_ != nullptr && trim_out_->out() > in_) {
|
||||
trim_out_old_length_ = trim_out_->length();
|
||||
trim_out_new_length_ = in_ - trim_out_->in();
|
||||
}
|
||||
|
||||
track_->BlockInvalidateCache();
|
||||
|
||||
// If we're splicing, trim_in_ is a copy
|
||||
if (splice_ != nullptr) {
|
||||
track_->AddBlockToGraph(trim_in_);
|
||||
Node::CopyInputs(splice_, trim_in_);
|
||||
|
||||
track_->InsertBlockAfter(trim_in_, splice_);
|
||||
}
|
||||
|
||||
// If we picked up a block to trim the in point of
|
||||
if (trim_in_old_length_ != trim_in_new_length_) {
|
||||
trim_in_->set_length_and_media_in(trim_in_new_length_);
|
||||
}
|
||||
|
||||
// Remove all blocks that are flagged for removal
|
||||
foreach (Block* remove_block, removed_blocks_) {
|
||||
track_->RippleRemoveBlock(remove_block);
|
||||
}
|
||||
|
||||
// If we picked up a block to trim the out point of
|
||||
if (trim_out_old_length_ != trim_out_new_length_) {
|
||||
trim_out_->set_length(trim_out_new_length_);
|
||||
}
|
||||
|
||||
// If we were given a block to insert, insert it here
|
||||
if (insert_ != nullptr) {
|
||||
track_->AddBlockToGraph(insert_);
|
||||
|
||||
if (trim_out_ == nullptr) {
|
||||
// This is the start of the Sequence
|
||||
track_->PrependBlock(insert_);
|
||||
} else if (trim_in_ == nullptr) {
|
||||
// This is the end of the Sequence
|
||||
track_->AppendBlock(insert_);
|
||||
} else {
|
||||
// This is somewhere in the middle of the Sequence
|
||||
track_->InsertBlockBetweenBlocks(insert_, trim_out_, trim_in_);
|
||||
}
|
||||
}
|
||||
|
||||
track_->UnblockInvalidateCache();
|
||||
|
||||
track_->InvalidateCache(in_, out_);
|
||||
}
|
||||
|
||||
void TrackRippleRemoveAreaCommand::undo()
|
||||
{
|
||||
track_->BlockInvalidateCache();
|
||||
|
||||
// If we were given a block to insert, insert it here
|
||||
if (insert_ != nullptr) {
|
||||
track_->RippleRemoveBlock(insert_);
|
||||
}
|
||||
|
||||
// If we picked up a block to trim the out point of
|
||||
if (trim_out_old_length_ != trim_out_new_length_) {
|
||||
trim_out_->set_length(trim_out_old_length_);
|
||||
}
|
||||
|
||||
// Remove all blocks that are flagged for removal
|
||||
foreach (Block* remove_block, removed_blocks_) {
|
||||
if (trim_in_ == nullptr) {
|
||||
track_->AppendBlock(remove_block);
|
||||
} else {
|
||||
track_->InsertBlockBefore(remove_block, trim_in_);
|
||||
}
|
||||
}
|
||||
|
||||
// If we picked up a block to trim the in point of
|
||||
if (trim_in_old_length_ != trim_in_new_length_) {
|
||||
trim_in_->set_length_and_media_in(trim_in_old_length_);
|
||||
}
|
||||
|
||||
// If we're splicing, trim_in_ is a copy
|
||||
if (splice_ != nullptr) {
|
||||
track_->RippleRemoveBlock(trim_in_);
|
||||
|
||||
// Remove node
|
||||
TakeNodeFromParentGraph(trim_in_, &memory_manager_);
|
||||
}
|
||||
|
||||
track_->UnblockInvalidateCache();
|
||||
|
||||
track_->InvalidateCache(in_, out_);
|
||||
}
|
||||
|
||||
TrackPlaceBlockCommand::TrackPlaceBlockCommand(TimelineOutput* timeline, int track, Block *block, rational in, QUndoCommand *parent) :
|
||||
TrackRippleRemoveAreaCommand(nullptr, in, 0, parent), // Out gets set correctly in redo()
|
||||
timeline_(timeline),
|
||||
track_index_(track),
|
||||
gap_(nullptr)
|
||||
{
|
||||
insert_ = block;
|
||||
insert_->setParent(&memory_manager_);
|
||||
}
|
||||
|
||||
void TrackPlaceBlockCommand::redo()
|
||||
{
|
||||
added_track_count_ = 0;
|
||||
|
||||
// Get track (or make it if necessary)
|
||||
while (track_index_ >= timeline_->Tracks().size()) {
|
||||
timeline_->AddTrack();
|
||||
|
||||
added_track_count_++;
|
||||
}
|
||||
|
||||
track_ = timeline_->TrackAt(track_index_);
|
||||
|
||||
append_ = (in_ >= track_->in());
|
||||
|
||||
// Check if the placement location is past the end of the timeline
|
||||
if (append_) {
|
||||
if (in_ > track_->in()) {
|
||||
// If so, insert a gap here
|
||||
gap_ = new GapBlock();
|
||||
gap_->set_length(in_ - track_->in());
|
||||
track_->AppendBlock(gap_);
|
||||
}
|
||||
|
||||
track_->AppendBlock(insert_);
|
||||
} else {
|
||||
out_ = in_ + insert_->length();
|
||||
|
||||
// Place the Block at this point
|
||||
TrackRippleRemoveAreaCommand::redo();
|
||||
}
|
||||
}
|
||||
|
||||
void TrackPlaceBlockCommand::undo()
|
||||
{
|
||||
if (append_) {
|
||||
track_->RippleRemoveBlock(insert_);
|
||||
TakeNodeFromParentGraph(insert_, &memory_manager_);
|
||||
|
||||
if (gap_ != nullptr) {
|
||||
track_->RippleRemoveBlock(gap_);
|
||||
delete TakeNodeFromParentGraph(gap_);
|
||||
}
|
||||
} else {
|
||||
TrackRippleRemoveAreaCommand::undo();
|
||||
}
|
||||
|
||||
for (;added_track_count_>0;added_track_count_--) {
|
||||
timeline_->RemoveTrack();
|
||||
}
|
||||
}
|
||||
|
||||
BlockSplitCommand::BlockSplitCommand(TrackOutput* track, Block *block, rational point, QUndoCommand *parent) :
|
||||
QUndoCommand(parent),
|
||||
track_(track),
|
||||
block_(block),
|
||||
new_length_(point - block->in()),
|
||||
old_length_(block->length())
|
||||
{
|
||||
Q_ASSERT(point > block_->in() && point < block_->out());
|
||||
|
||||
// Ensures that this block is deleted if this action is undone
|
||||
new_block_ = CreateSplitBlock(block_, point, &memory_manager_);
|
||||
}
|
||||
|
||||
void BlockSplitCommand::redo()
|
||||
{
|
||||
track_->BlockInvalidateCache();
|
||||
|
||||
block_->set_length(new_length_);
|
||||
|
||||
track_->AddBlockToGraph(new_block_);
|
||||
Node::CopyInputs(block_, new_block_);
|
||||
|
||||
// Will re-parent new_block_ to the track's graph
|
||||
track_->InsertBlockAfter(new_block_, block_);
|
||||
|
||||
track_->UnblockInvalidateCache();
|
||||
}
|
||||
|
||||
void BlockSplitCommand::undo()
|
||||
{
|
||||
track_->BlockInvalidateCache();
|
||||
|
||||
block_->set_length(old_length_);
|
||||
track_->RippleRemoveBlock(new_block_);
|
||||
|
||||
TakeNodeFromParentGraph(new_block_, &memory_manager_);
|
||||
|
||||
track_->UnblockInvalidateCache();
|
||||
}
|
||||
|
||||
TrackSplitAtTimeCommand::TrackSplitAtTimeCommand(TrackOutput *track, rational point, QUndoCommand *parent) :
|
||||
QUndoCommand(parent)
|
||||
{
|
||||
// Find Block that contains this time
|
||||
for (int i=0;i<track->Blocks().size();i++) {
|
||||
Block* b = track->Blocks().at(i);
|
||||
|
||||
if (b->out() == point) {
|
||||
// This time is between blocks, no split needs to occur
|
||||
return;
|
||||
} else if (b->in() < point && b->out() > point) {
|
||||
// We found the Block, split it
|
||||
new BlockSplitCommand(track, b, point, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TrackReplaceBlockCommand::TrackReplaceBlockCommand(TrackOutput* track, Block *old, Block *replace, QUndoCommand *parent) :
|
||||
QUndoCommand(parent),
|
||||
track_(track),
|
||||
old_(old),
|
||||
replace_(replace)
|
||||
{
|
||||
}
|
||||
|
||||
void TrackReplaceBlockCommand::redo()
|
||||
{
|
||||
track_->ReplaceBlock(old_, replace_);
|
||||
}
|
||||
|
||||
void TrackReplaceBlockCommand::undo()
|
||||
{
|
||||
track_->ReplaceBlock(replace_, old_);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef TIMELINEUNDOABLE_H
|
||||
#define TIMELINEUNDOABLE_H
|
||||
|
||||
#include <QUndoCommand>
|
||||
|
||||
#include "node/block/block.h"
|
||||
#include "node/block/gap/gap.h"
|
||||
#include "node/output/timeline/timeline.h"
|
||||
#include "node/output/track/track.h"
|
||||
|
||||
class BlockResizeCommand : public QUndoCommand {
|
||||
public:
|
||||
BlockResizeCommand(Block* block, rational new_length, QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Block* block_;
|
||||
rational old_length_;
|
||||
rational new_length_;
|
||||
};
|
||||
|
||||
class BlockResizeWithMediaInCommand : public QUndoCommand {
|
||||
public:
|
||||
BlockResizeWithMediaInCommand(Block* block, rational new_length, QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Block* block_;
|
||||
rational old_length_;
|
||||
rational new_length_;
|
||||
};
|
||||
|
||||
class BlockSetMediaInCommand : public QUndoCommand {
|
||||
public:
|
||||
BlockSetMediaInCommand(Block* block, rational new_media_in, QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Block* block_;
|
||||
rational old_media_in_;
|
||||
rational new_media_in_;
|
||||
};
|
||||
|
||||
class TrackRippleRemoveBlockCommand : public QUndoCommand {
|
||||
public:
|
||||
TrackRippleRemoveBlockCommand(TrackOutput* track, Block* block, QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
TrackOutput* track_;
|
||||
|
||||
Block* block_;
|
||||
|
||||
Block* before_;
|
||||
Block* after_;
|
||||
};
|
||||
|
||||
class TrackInsertBlockBetweenBlocksCommand : public QUndoCommand {
|
||||
public:
|
||||
TrackInsertBlockBetweenBlocksCommand(TrackOutput* track, Block* block, Block* before, Block* after, QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
TrackOutput* track_;
|
||||
|
||||
Block* block_;
|
||||
|
||||
Block* before_;
|
||||
Block* after_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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`.
|
||||
*/
|
||||
class TrackRippleRemoveAreaCommand : public QUndoCommand {
|
||||
public:
|
||||
TrackRippleRemoveAreaCommand(TrackOutput* track, rational in, rational out, QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
protected:
|
||||
TrackOutput* track_;
|
||||
rational in_;
|
||||
rational out_;
|
||||
|
||||
Block* splice_;
|
||||
|
||||
Block* trim_out_;
|
||||
QVector<Block*> removed_blocks_;
|
||||
Block* trim_in_;
|
||||
|
||||
rational trim_in_old_length_;
|
||||
rational trim_out_old_length_;
|
||||
|
||||
rational trim_in_new_length_;
|
||||
rational trim_out_new_length_;
|
||||
|
||||
Block* insert_;
|
||||
|
||||
QObject memory_manager_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
class TrackPlaceBlockCommand : public TrackRippleRemoveAreaCommand {
|
||||
public:
|
||||
TrackPlaceBlockCommand(TimelineOutput *timeline, int track, Block* block, rational in, QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
TimelineOutput* timeline_;
|
||||
int track_index_;
|
||||
bool append_;
|
||||
GapBlock* gap_;
|
||||
int added_track_count_;
|
||||
};
|
||||
|
||||
class BlockSplitCommand : public QUndoCommand {
|
||||
public:
|
||||
BlockSplitCommand(TrackOutput* track, Block* block, rational point, QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
TrackOutput* track_;
|
||||
Block* block_;
|
||||
|
||||
rational new_length_;
|
||||
rational old_length_;
|
||||
|
||||
Block* new_block_;
|
||||
|
||||
QObject memory_manager_;
|
||||
};
|
||||
|
||||
class TrackSplitAtTimeCommand : public QUndoCommand {
|
||||
public:
|
||||
TrackSplitAtTimeCommand(TrackOutput* track, rational point, QUndoCommand* parent = nullptr);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Replaces Block `old` with Block `replace`
|
||||
*
|
||||
* Both blocks must have equal lengths.
|
||||
*/
|
||||
class TrackReplaceBlockCommand : public QUndoCommand {
|
||||
public:
|
||||
TrackReplaceBlockCommand(TrackOutput* track, Block* old, Block* replace, QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
TrackOutput* track_;
|
||||
Block* old_;
|
||||
Block* replace_;
|
||||
};
|
||||
|
||||
#endif // TIMELINEUNDOABLE_H
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <QtMath>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "viewersizer.h"
|
||||
|
||||
ViewerWidget::ViewerWidget(QWidget *parent) :
|
||||
@@ -94,7 +95,7 @@ const double &ViewerWidget::scale()
|
||||
|
||||
rational ViewerWidget::GetTime()
|
||||
{
|
||||
return rational(ruler_->GetTime()) * time_base_;
|
||||
return olive::timestamp_to_time(ruler_->GetTime(), time_base_);
|
||||
}
|
||||
|
||||
void ViewerWidget::SetScale(const double &scale_)
|
||||
|
||||
Reference in New Issue
Block a user