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
+32 -9
View File
@@ -28,7 +28,8 @@ QString padded(int arg, int padding) {
QString olive::timestamp_to_timecode(const int64_t &timestamp,
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 &timestamp,
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 &timestamp,
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 &timestamp,
return QString();
}
rational olive::timestamp_to_time(const int64_t &timestamp, 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());
}
+5 -1
View File
@@ -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 &timestamp, const rational& timebase, const TimecodeDisplay& display);
QString timestamp_to_timecode(const int64_t &timestamp, 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);
}
+2 -1
View File
@@ -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);
+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
*
+1 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -5
View File
@@ -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;
}
+1
View File
@@ -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}
+16 -15
View File
@@ -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();
+21 -1
View File
@@ -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
+20 -1
View File
@@ -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();
+23 -5
View File
@@ -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,
+18 -4
View File
@@ -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;
}
+11 -12
View File
@@ -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)
+95
View File
@@ -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
)
+416
View File
@@ -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_);
}
+202
View File
@@ -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
+2 -1
View File
@@ -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_)