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
@@ -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);
}
}
}