/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/timelinewidget/timelinewidget.h"
#include
#include
#include "common/clamp.h"
#include "common/flipmodifiers.h"
#include "common/qtutils.h"
#include "common/range.h"
#include "common/timecodefunctions.h"
#include "config/config.h"
#include "core.h"
#include "node/block/gap/gap.h"
#include "node/block/transition/transition.h"
#include "pointer.h"
#include "widget/nodeview/nodeviewundo.h"
#include "widget/timelinewidget/undo/timelineundopointer.h"
namespace olive {
PointerTool::PointerTool(TimelineWidget *parent) :
TimelineTool(parent),
movement_allowed_(true),
trimming_allowed_(true),
track_movement_allowed_(true),
gap_trimming_allowed_(false),
rubberband_selecting_(false)
{
}
void PointerTool::MousePress(TimelineViewMouseEvent *event)
{
const Track::Reference& track_ref = event->GetTrack();
// Determine if item clicked on is selectable
clicked_item_ = parent()->GetItemAtScenePos(event->GetCoordinates());
can_rubberband_select_ = false;
bool selectable_item = (clicked_item_
&& !parent()->GetTrackFromReference(track_ref)->IsLocked());
if (selectable_item) {
// Cache the clip's type for use later
drag_track_type_ = track_ref.type();
// If we haven't started dragging yet, we'll initiate a drag here
// Record where the drag started in timeline coordinates
drag_start_ = event->GetCoordinates();
// Determine whether we're trimming or moving based on the position of the cursor
drag_movement_mode_ = IsCursorInTrimHandle(clicked_item_,
event->GetSceneX());
// If we're not in a trim mode, we must be in a move mode (provided the tool allows movement and
// the block is not a gap)
if (drag_movement_mode_ == Timeline::kNone
&& movement_allowed_
&& !dynamic_cast(clicked_item_)) {
drag_movement_mode_ = Timeline::kMove;
}
// If this item is already selected, no further selection needs to be made
if (parent()->IsBlockSelected(clicked_item_)) {
// Collect item deselections
QVector deselected_blocks;
// If shift is held, deselect it
if (event->GetModifiers() & Qt::ShiftModifier) {
parent()->RemoveSelection(clicked_item_);
deselected_blocks.append(clicked_item_);
// If not holding alt, deselect all links as well
if (!(event->GetModifiers() & Qt::AltModifier)) {
parent()->SetBlockLinksSelected(clicked_item_, false);
deselected_blocks.append(clicked_item_->block_links());
}
}
parent()->SignalDeselectedBlocks(deselected_blocks);
return;
}
}
// If not holding shift, deselect all clips
if (!(event->GetModifiers() & Qt::ShiftModifier)) {
parent()->DeselectAll();
}
if (selectable_item) {
// Collect item selections
QVector selected_blocks;
// Select this item
parent()->AddSelection(clicked_item_);
selected_blocks.append(clicked_item_);
// If not holding alt, select all links as well
if (!(event->GetModifiers() & Qt::AltModifier)) {
parent()->SetBlockLinksSelected(clicked_item_, true);
selected_blocks.append(clicked_item_->block_links());
}
parent()->SignalSelectedBlocks(selected_blocks);
}
can_rubberband_select_ = (event->GetButton() == Qt::LeftButton // Only rubberband select from the primary mouse button
&& (!selectable_item || drag_movement_mode_ == Timeline::kNone)); // And if no item was selected OR the item isn't draggable
if (can_rubberband_select_) {
drag_global_start_ = QCursor::pos();
}
}
void PointerTool::MouseMove(TimelineViewMouseEvent *event)
{
if (can_rubberband_select_) {
if (!rubberband_selecting_) {
// If we clicked an item but are rubberband selecting anyway, deselect it now
if (clicked_item_) {
parent()->RemoveSelection(clicked_item_);
parent()->SignalDeselectedBlocks({clicked_item_});
clicked_item_ = nullptr;
}
parent()->StartRubberBandSelect(drag_global_start_);
rubberband_selecting_ = true;
}
// Process rubberband select
parent()->MoveRubberBandSelect(true, !(event->GetModifiers() & Qt::AltModifier));
} else {
// Process drag
if (!dragging_) {
// Now that the cursor has moved, we will assume the intention is to drag
// Clear snap points
snap_points_.clear();
// If we're performing an action, we can initiate ghosts
if (drag_movement_mode_ != Timeline::kNone) {
InitiateDrag(clicked_item_, drag_movement_mode_);
}
// Set dragging to true here so no matter what, the drag isn't re-initiated until it's completed
dragging_ = true;
}
if (dragging_ && !parent()->GetGhostItems().isEmpty()) {
// We're already dragging AND we have ghosts to work with
ProcessDrag(event->GetCoordinates());
}
}
}
void PointerTool::MouseRelease(TimelineViewMouseEvent *event)
{
if (rubberband_selecting_) {
// Finish rubberband select
parent()->EndRubberBandSelect();
rubberband_selecting_ = false;
return;
}
if (dragging_) {
// If we were dragging, process the end of the drag
if (!parent()->GetGhostItems().isEmpty()) {
FinishDrag(event);
}
// Clean up
parent()->ClearGhosts();
snap_points_.clear();
dragging_ = false;
}
}
void PointerTool::HoverMove(TimelineViewMouseEvent *event)
{
if (trimming_allowed_) {
// No dragging, but we still want to process cursors
Block* block_at_cursor = parent()->GetItemAtScenePos(event->GetCoordinates());
if (block_at_cursor) {
switch (IsCursorInTrimHandle(block_at_cursor, event->GetSceneX())) {
case Timeline::kTrimIn:
parent()->setCursor(Qt::SizeHorCursor);
break;
case Timeline::kTrimOut:
parent()->setCursor(Qt::SizeHorCursor);
break;
default:
parent()->unsetCursor();
}
} else {
parent()->unsetCursor();
}
} else {
parent()->unsetCursor();
}
}
void SetGhostToSlideMode(TimelineViewGhostItem* g)
{
g->SetCanMoveTracks(false);
g->SetData(TimelineViewGhostItem::kGhostIsSliding, true);
}
void PointerTool::InitiateDragInternal(Block *clicked_item,
Timeline::MovementMode trim_mode,
bool dont_roll_trims,
bool allow_nongap_rolling,
bool slide_instead_of_moving)
{
// Get list of selected blocks
QVector clips = parent()->GetSelectedBlocks();
if (trim_mode == Timeline::kMove) {
// Gaps are not allowed to move, and since we only allow moving one block type at a time,
// dragging a gap is a no-op
if (dynamic_cast(clicked_item)) {
return;
}
// Determine if this move is a slide, which is determined by either
bool clips_are_sliding = (slide_instead_of_moving || dynamic_cast(clicked_item));
if (clips_are_sliding) {
// This is a slide. What we do here is move clips within their own track, between the clips
// that they're already next to. We don't allow changing tracks or changing the order of
// blocks.
//
// For slides to be legal, we make all blocks "contiguous". This means that only one series
// of blocks can move at a time and prevents.
QHash