work towards making timeline actions all undoable
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user