From cdcfd59ec54dd8fbc47c46bdf13fb5e79d51214b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 18 Sep 2019 02:39:26 +1000 Subject: [PATCH] started ripple tool and finish hand tool --- app/widget/timelineview/timelineview.cpp | 6 +- app/widget/timelineview/timelineview.h | 36 ++++- app/widget/timelineview/tool/CMakeLists.txt | 2 + app/widget/timelineview/tool/hand.cpp | 54 +++++++ app/widget/timelineview/tool/pointer.cpp | 158 ++++++++++++++------ app/widget/timelineview/tool/ripple.cpp | 60 ++++++++ 6 files changed, 265 insertions(+), 51 deletions(-) create mode 100644 app/widget/timelineview/tool/hand.cpp create mode 100644 app/widget/timelineview/tool/ripple.cpp diff --git a/app/widget/timelineview/timelineview.cpp b/app/widget/timelineview/timelineview.cpp index 53725899b..7bf6e7ff6 100644 --- a/app/widget/timelineview/timelineview.cpp +++ b/app/widget/timelineview/timelineview.cpp @@ -36,7 +36,9 @@ TimelineView::TimelineView(QWidget *parent) : QGraphicsView(parent), pointer_tool_(this), import_tool_(this), + ripple_tool_(this), razor_tool_(this), + hand_tool_(this), playhead_(0) { setScene(&scene_); @@ -206,7 +208,7 @@ TimelineView::Tool *TimelineView::GetActiveTool() case olive::tool::kEdit: return nullptr; // FIXME: Implement case olive::tool::kRipple: - return nullptr; // FIXME: Implement + return &ripple_tool_; case olive::tool::kRolling: return nullptr; // FIXME: Implement case olive::tool::kRazor: @@ -216,7 +218,7 @@ TimelineView::Tool *TimelineView::GetActiveTool() case olive::tool::kSlide: return nullptr; // FIXME: Implement case olive::tool::kHand: - return nullptr; // FIXME: Implement + return &hand_tool_; case olive::tool::kZoom: return nullptr; // FIXME: Implement case olive::tool::kTransition: diff --git a/app/widget/timelineview/timelineview.h b/app/widget/timelineview/timelineview.h index 1401f7c30..3f004bdec 100644 --- a/app/widget/timelineview/timelineview.h +++ b/app/widget/timelineview/timelineview.h @@ -169,11 +169,16 @@ private: public: PointerTool(TimelineView* parent); - virtual void MousePress(QMouseEvent *event); - virtual void MouseMove(QMouseEvent *event); - virtual void MouseRelease(QMouseEvent *event); + virtual void MousePress(QMouseEvent *event) override; + virtual void MouseMove(QMouseEvent *event) override; + virtual void MouseRelease(QMouseEvent *event) override; + protected: + void SetMovementAllowed(bool allowed); + virtual void MouseReleaseInternal(QMouseEvent *event); + virtual rational FrameValidateInternal(rational time_movement, QVector); private: int track_start_; + bool movement_allowed_; }; class ImportTool : public Tool @@ -199,6 +204,29 @@ private: virtual void MouseRelease(QMouseEvent *event); }; + class RippleTool : public PointerTool + { + public: + RippleTool(TimelineView* parent); + protected: + virtual void MouseReleaseInternal(QMouseEvent *event); + virtual rational FrameValidateInternal(rational time_movement, QVector ghosts); + }; + + class HandTool : public Tool + { + public: + HandTool(TimelineView* parent); + + virtual void MousePress(QMouseEvent *event); + virtual void MouseMove(QMouseEvent *event); + virtual void MouseRelease(QMouseEvent *event); + + private: + QPoint screen_drag_start_; + QPoint scrollbar_start_; + }; + Tool* GetActiveTool(); int GetTrackY(int track_index); @@ -206,7 +234,9 @@ private: PointerTool pointer_tool_; ImportTool import_tool_; + RippleTool ripple_tool_; RazorTool razor_tool_; + HandTool hand_tool_; void AddGhost(TimelineViewGhostItem* ghost); diff --git a/app/widget/timelineview/tool/CMakeLists.txt b/app/widget/timelineview/tool/CMakeLists.txt index 5bfeda7f6..652417678 100644 --- a/app/widget/timelineview/tool/CMakeLists.txt +++ b/app/widget/timelineview/tool/CMakeLists.txt @@ -16,9 +16,11 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} + widget/timelineview/tool/hand.cpp widget/timelineview/tool/import.cpp widget/timelineview/tool/pointer.cpp widget/timelineview/tool/razor.cpp + widget/timelineview/tool/ripple.cpp widget/timelineview/tool/tool.cpp PARENT_SCOPE ) diff --git a/app/widget/timelineview/tool/hand.cpp b/app/widget/timelineview/tool/hand.cpp new file mode 100644 index 000000000..b7486dc1b --- /dev/null +++ b/app/widget/timelineview/tool/hand.cpp @@ -0,0 +1,54 @@ +/*** + + 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 . + +***/ + +#include "widget/timelineview/timelineview.h" + +#include + +TimelineView::HandTool::HandTool(TimelineView* parent) : + Tool(parent) +{ + +} + +void TimelineView::HandTool::MousePress(QMouseEvent *event) +{ + screen_drag_start_ = event->pos(); + scrollbar_start_.setX(parent()->horizontalScrollBar()->value()); + scrollbar_start_.setY(parent()->verticalScrollBar()->value()); + dragging_ = true; +} + +void TimelineView::HandTool::MouseMove(QMouseEvent *event) +{ + if (dragging_) { + QPoint drag_diff = event->pos() - screen_drag_start_; + + parent()->horizontalScrollBar()->setValue(scrollbar_start_.x() - drag_diff.x()); + parent()->verticalScrollBar()->setValue(scrollbar_start_.y() - drag_diff.y()); + } +} + +void TimelineView::HandTool::MouseRelease(QMouseEvent *event) +{ + Q_UNUSED(event) + + dragging_ = false; +} diff --git a/app/widget/timelineview/tool/pointer.cpp b/app/widget/timelineview/tool/pointer.cpp index 7176ac4fc..61d348dd3 100644 --- a/app/widget/timelineview/tool/pointer.cpp +++ b/app/widget/timelineview/tool/pointer.cpp @@ -27,7 +27,8 @@ #include "node/block/gap/gap.h" TimelineView::PointerTool::PointerTool(TimelineView *parent) : - Tool(parent) + Tool(parent), + movement_allowed_(true) { } @@ -45,94 +46,125 @@ void FlipControlAndShiftModifiers(QMouseEvent* e) { void TimelineView::PointerTool::MousePress(QMouseEvent *event) { + // We use Shift for multiple selection while Qt uses Ctrl, we flip those modifiers here to compensate FlipControlAndShiftModifiers(event); + // Default QGraphicsView behavior (item selection) parent()->QGraphicsView::mousePressEvent(event); + + // We don't initiate dragging here since clicking could easily be just for selecting } void TimelineView::PointerTool::MouseMove(QMouseEvent *event) { + // We use Shift for multiple selection while Qt uses Ctrl, we flip those modifiers here to compensate FlipControlAndShiftModifiers(event); + // Default QGraphicsView behavior (item selection) parent()->QGraphicsView::mouseMoveEvent(event); + // Now that the cursor has moved, we will assume the intention is to drag if (!dragging_) { - drag_start_ = GetScenePos(event->pos()); - track_start_ = parent()->SceneToTrack(drag_start_.y()); + // If we haven't started dragging yet, we'll initiate a drag here - TimelineViewRect* clicked_item = static_cast(GetItemAtScenePos(drag_start_)); + // Get the item that was clicked + TimelineViewRect* clicked_item = dynamic_cast(GetItemAtScenePos(drag_start_)); - snap_points_.clear(); + // We only initiate a pointer drag if the user actually dragged an item, otherwise if they dragged on empty space + // QGraphicsView default behavior would initiate a rubberband drag - // Let's see if there's anything selected to drag if (clicked_item != nullptr) { - TimelineViewGhostItem::Mode trim_mode; + // Clear snap points + snap_points_.clear(); - if (drag_start_.x() < clicked_item->x() + clicked_item->rect().left() + 20) { + // Record where the drag started in timeline coordinates + drag_start_ = GetScenePos(event->pos()); + track_start_ = parent()->SceneToTrack(drag_start_.y()); + + // Determine whether we're trimming or moving based on the position of the cursor + TimelineViewGhostItem::Mode trim_mode = TimelineViewGhostItem::kNone; + + // FIXME: Hardcoded number + const int kTrimHandle = 20; + + if (drag_start_.x() < clicked_item->x() + kTrimHandle) { trim_mode = TimelineViewGhostItem::kTrimIn; - } else if (drag_start_.x() > clicked_item->x() + clicked_item->rect().right() - 20) { + } else if (drag_start_.x() > clicked_item->x() + clicked_item->rect().right() - kTrimHandle) { trim_mode = TimelineViewGhostItem::kTrimOut; - } else { + } else if (movement_allowed_) { + // Some derived classes don't allow movement trim_mode = TimelineViewGhostItem::kMove; } - QList selected_items = parent()->scene_.selectedItems(); + // Make sure we can actually perform an action here + if (trim_mode != TimelineViewGhostItem::kNone) { + QList selected_items = parent()->scene_.selectedItems(); - foreach (QGraphicsItem* item, selected_items) { - TimelineViewClipItem* clip_item = static_cast(item); - TimelineViewGhostItem* ghost = TimelineViewGhostItem::FromClip(clip_item); + // For each selected item, create a "ghost", a visual representation of the action before it gets performed + foreach (QGraphicsItem* item, selected_items) { + TimelineViewClipItem* clip_item = dynamic_cast(item); + TimelineViewGhostItem* ghost = TimelineViewGhostItem::FromClip(clip_item); - ghost->SetScale(parent()->scale_); + ghost->SetScale(parent()->scale_); - // Determine correct mode for ghost - if (trim_mode == TimelineViewGhostItem::kMove // Movement is indiscriminate, all the ghosts can be set to this - || clip_item == clicked_item) { // Trimming should only be the currently clicked Block - ghost->SetMode(trim_mode); + // Determine correct mode for ghost + // Movement is indiscriminate, all the ghosts can be set to do this, however trimming should be limited to + // the currently clicked Block since multiple clips trimming at once could get ugly + if (trim_mode == TimelineViewGhostItem::kMove + || clip_item == clicked_item) { + ghost->SetMode(trim_mode); - switch (trim_mode) { - case TimelineViewGhostItem::kMove: - snap_points_.append(ghost->In()); - snap_points_.append(ghost->Out()); - break; - case TimelineViewGhostItem::kTrimIn: - snap_points_.append(ghost->In()); - break; - case TimelineViewGhostItem::kTrimOut: - snap_points_.append(ghost->Out()); - break; - default: - break; + // Prepare snap points (optimizes snapping for later) + switch (trim_mode) { + case TimelineViewGhostItem::kMove: + snap_points_.append(ghost->In()); + snap_points_.append(ghost->Out()); + break; + case TimelineViewGhostItem::kTrimIn: + snap_points_.append(ghost->In()); + break; + case TimelineViewGhostItem::kTrimOut: + snap_points_.append(ghost->Out()); + break; + default: + break; + } + } else { + ghost->SetMode(TimelineViewGhostItem::kNone); } - } else { - ghost->SetMode(TimelineViewGhostItem::kNone); + + parent()->ghost_items_.append(ghost); + parent()->scene_.addItem(ghost); } - parent()->ghost_items_.append(ghost); - parent()->scene_.addItem(ghost); } } + // Set dragging to true here so no matter what, the drag isn't re-initiated until it's completed dragging_ = true; } else if (!parent()->ghost_items_.isEmpty()) { - QPointF scene_pos = GetScenePos(event->pos()); + // We're already dragging AND we have ghosts to work with + + // Retrieve cursor position difference + QPointF scene_pos = GetScenePos(event->pos()); + QPointF movement = scene_pos - drag_start_; + + // Determine track movement int cursor_track = parent()->SceneToTrack(scene_pos.y()); int track_movement = cursor_track - track_start_; - QPointF movement = scene_pos - drag_start_; - + // Determine frame movement rational time_movement = parent()->SceneToTime(movement.x()); - // Validate movement - time_movement = ValidateFrameMovement(time_movement, parent()->ghost_items_); - time_movement = ValidateInTrimming(time_movement, parent()->ghost_items_); - time_movement = ValidateOutTrimming(time_movement, parent()->ghost_items_); + // Validate movement (enforce all ghosts moving in legal ways) + time_movement = FrameValidateInternal(time_movement, parent()->ghost_items_); track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_); - // Perform snapping if enabled + // Perform snapping if enabled (adjusts time_movement if it's close to any potential snap points) if (olive::core.snapping()) { SnapPoint(snap_points_, &time_movement); } @@ -153,6 +185,7 @@ void TimelineView::PointerTool::MouseMove(QMouseEvent *event) ghost->SetInAdjustment(time_movement); ghost->SetOutAdjustment(time_movement); + // Track movement is only legal for moving, not for trimming ghost->SetTrackAdjustment(track_movement); int track = ghost->GetAdjustedTrack(); ghost->SetY(parent()->GetTrackY(track)); @@ -166,12 +199,37 @@ void TimelineView::PointerTool::MouseMove(QMouseEvent *event) void TimelineView::PointerTool::MouseRelease(QMouseEvent *event) { + // We use Shift for multiple selection while Qt uses Ctrl, we flip those modifiers here to compensate FlipControlAndShiftModifiers(event); + // Default QGraphicsView behavior (item selection) parent()->QGraphicsView::mouseReleaseEvent(event); + MouseReleaseInternal(event); + + if (dragging_) { + parent()->ClearGhosts(); + snap_points_.clear(); + } + + dragging_ = false; +} + +void TimelineView::PointerTool::SetMovementAllowed(bool allowed) +{ + movement_allowed_ = allowed; +} + +void TimelineView::PointerTool::MouseReleaseInternal(QMouseEvent *event) +{ + Q_UNUSED(event) + + // We create a QObject on the stack so that when we allocate objects on the heap, they aren't parent-less and will + // get cleaned up if they aren't re-parented by the attached NodeGraph QObject block_memory_manager; + // 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_) { Block* b = Node::ValueToPtr(ghost->data(0)); @@ -183,6 +241,8 @@ void TimelineView::PointerTool::MouseRelease(QMouseEvent *event) emit parent()->RequestReplaceBlock(b, gap, ghost->Track()); } + // Now we place the clips back in the timeline where the user moved them. It's legal for them to overwrite parts or + // all of the gaps we inserted earlier foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { Block* b = Node::ValueToPtr(ghost->data(0)); @@ -199,8 +259,14 @@ void TimelineView::PointerTool::MouseRelease(QMouseEvent *event) emit parent()->RequestPlaceBlock(b, ghost->GetAdjustedIn(), ghost->GetAdjustedTrack()); } +} - parent()->ClearGhosts(); +rational TimelineView::PointerTool::FrameValidateInternal(rational time_movement, QVector) +{ + // Default behavior is to validate all movement and trimming + time_movement = ValidateFrameMovement(time_movement, parent()->ghost_items_); + time_movement = ValidateInTrimming(time_movement, parent()->ghost_items_); + time_movement = ValidateOutTrimming(time_movement, parent()->ghost_items_); - dragging_ = false; + return time_movement; } diff --git a/app/widget/timelineview/tool/ripple.cpp b/app/widget/timelineview/tool/ripple.cpp new file mode 100644 index 000000000..65a687ffe --- /dev/null +++ b/app/widget/timelineview/tool/ripple.cpp @@ -0,0 +1,60 @@ +/*** + + 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 . + +***/ + +#include "widget/timelineview/timelineview.h" + +TimelineView::RippleTool::RippleTool(TimelineView* parent) : + PointerTool(parent) +{ + SetMovementAllowed(false); +} + +void TimelineView::RippleTool::MouseReleaseInternal(QMouseEvent *event) +{ + Q_UNUSED(event) + + // Retrieve cursor position difference + QPointF scene_pos = GetScenePos(event->pos()); + QPointF movement = scene_pos - drag_start_; + + // The point to ripple all clips after (we use the earliest point possible) + rational ripple_point = RATIONAL_MAX; + + // The amount to ripple by + rational ripple_length = parent()->SceneToTime(movement.x()); + + // Find earliest point to ripple around + foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + if (ghost->mode() == TimelineViewGhostItem::kTrimIn) { + ripple_point = qMin(ripple_point, ghost->In()); + } else if (ghost->mode() == TimelineViewGhostItem::kTrimOut) { + ripple_point = qMin(ripple_point, ghost->Out()); + } + } + + +} + +rational TimelineView::RippleTool::FrameValidateInternal(rational time_movement, QVector ghosts) +{ + // FIXME: Validate rippling + + return PointerTool::FrameValidateInternal(time_movement, ghosts); +}