work towards making timeline actions all undoable
This commit is contained in:
@@ -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