Merge branch 'master' into pr/1717
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
#include "tool/rolling.h"
|
||||
#include "tool/slide.h"
|
||||
#include "tool/slip.h"
|
||||
#include "tool/trackselect.h"
|
||||
#include "tool/transition.h"
|
||||
#include "tool/zoom.h"
|
||||
#include "tool/tool.h"
|
||||
@@ -59,6 +60,7 @@ namespace olive {
|
||||
|
||||
TimelineWidget::TimelineWidget(QWidget *parent) :
|
||||
super(true, true, parent),
|
||||
NodeCopyPasteService(QStringLiteral("timeline")),
|
||||
rubberband_(QRubberBand::Rectangle, this),
|
||||
active_tool_(nullptr),
|
||||
use_audio_time_units_(false),
|
||||
@@ -75,6 +77,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
|
||||
timecode_label_->SetAlignment(Qt::AlignCenter);
|
||||
timecode_label_->SetDisplayType(RationalSlider::kTime);
|
||||
timecode_label_->setVisible(false);
|
||||
timecode_label_->SetMinimum(0);
|
||||
connect(timecode_label_, &RationalSlider::ValueChanged, this, &TimelineWidget::SetTimeAndSignal);
|
||||
ruler_and_time_layout->addWidget(timecode_label_);
|
||||
|
||||
@@ -100,6 +103,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
|
||||
tools_.fill(nullptr);
|
||||
|
||||
tools_.replace(olive::Tool::kPointer, new PointerTool(this));
|
||||
tools_.replace(olive::Tool::kTrackSelect, new TrackSelectTool(this));
|
||||
tools_.replace(olive::Tool::kEdit, new EditTool(this));
|
||||
tools_.replace(olive::Tool::kRipple, new RippleTool(this));
|
||||
tools_.replace(olive::Tool::kRolling, new RollingTool(this));
|
||||
@@ -183,6 +187,14 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
|
||||
|
||||
connect(Core::instance(), &Core::ToolChanged, this, &TimelineWidget::ToolChanged);
|
||||
connect(Core::instance(), &Core::AddableObjectChanged, this, &TimelineWidget::AddableObjectChanged);
|
||||
|
||||
signal_block_change_timer_ = new QTimer(this);
|
||||
signal_block_change_timer_->setInterval(1);
|
||||
signal_block_change_timer_->setSingleShot(true);
|
||||
connect(signal_block_change_timer_, &QTimer::timeout, this, [this]{
|
||||
signal_block_change_timer_->stop();
|
||||
emit BlockSelectionChanged(selected_blocks_);
|
||||
});
|
||||
}
|
||||
|
||||
TimelineWidget::~TimelineWidget()
|
||||
@@ -471,7 +483,8 @@ void TimelineWidget::SplitAtPlayhead()
|
||||
void TimelineWidget::ReplaceBlocksWithGaps(const QVector<Block *> &blocks,
|
||||
bool remove_from_graph,
|
||||
MultiUndoCommand *command,
|
||||
bool handle_transitions)
|
||||
bool handle_transitions,
|
||||
bool handle_invalidations)
|
||||
{
|
||||
foreach (Block* b, blocks) {
|
||||
if (dynamic_cast<GapBlock*>(b)) {
|
||||
@@ -482,7 +495,7 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QVector<Block *> &blocks,
|
||||
|
||||
Track* original_track = b->track();
|
||||
|
||||
command->add_child(new TrackReplaceBlockWithGapCommand(original_track, b, handle_transitions));
|
||||
command->add_child(new TrackReplaceBlockWithGapCommand(original_track, b, handle_transitions, handle_invalidations));
|
||||
|
||||
if (remove_from_graph) {
|
||||
command->add_child(new NodeRemoveWithExclusiveDependenciesAndDisconnect(b));
|
||||
@@ -521,6 +534,9 @@ void TimelineWidget::DeleteSelected(bool ripple)
|
||||
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
// Remove all selections
|
||||
command->add_child(new SetSelectionsCommand(this, TimelineWidgetSelections(), GetSelections()));
|
||||
|
||||
// For transitions, remove them but extend their attached blocks to fill their place
|
||||
foreach (TransitionBlock* transition, transitions_to_delete) {
|
||||
TransitionRemoveCommand *trc = new TransitionRemoveCommand(transition, true);
|
||||
@@ -532,10 +548,7 @@ void TimelineWidget::DeleteSelected(bool ripple)
|
||||
}
|
||||
|
||||
// Replace clips with gaps (effectively deleting them)
|
||||
ReplaceBlocksWithGaps(clips_to_delete, true, command);
|
||||
|
||||
// Remove all selections
|
||||
command->add_child(new SetSelectionsCommand(this, TimelineWidgetSelections(), GetSelections(), false));
|
||||
ReplaceBlocksWithGaps(clips_to_delete, true, command, false, !ripple);
|
||||
|
||||
// Insert ripple command now that it's all cleaned up gaps
|
||||
TimelineRippleDeleteGapsAtRegionsCommand *ripple_command = nullptr;
|
||||
@@ -1184,7 +1197,8 @@ void TimelineWidget::SetScrollZoomsByDefaultOnAllViews(bool e)
|
||||
|
||||
void TimelineWidget::SignalBlockSelectionChange()
|
||||
{
|
||||
emit BlockSelectionChanged(selected_blocks_);
|
||||
signal_block_change_timer_->stop();
|
||||
signal_block_change_timer_->start();
|
||||
}
|
||||
|
||||
void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost)
|
||||
@@ -1231,7 +1245,7 @@ void TimelineWidget::NudgeInternal(rational amount)
|
||||
// Nudge selections
|
||||
TimelineWidgetSelections new_sel = GetSelections();
|
||||
new_sel.ShiftTime(amount);
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(this, new_sel, GetSelections(), true));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(this, new_sel, GetSelections()));
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
@@ -1283,7 +1297,7 @@ void TimelineWidget::MoveToPlayheadInternal(bool out)
|
||||
it.value().shift(track_adj);
|
||||
}
|
||||
}
|
||||
command->add_child(new SetSelectionsCommand(this, new_sel, GetSelections(), true));
|
||||
command->add_child(new SetSelectionsCommand(this, new_sel, GetSelections()));
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
@@ -1357,7 +1371,7 @@ void TimelineWidget::SignalSelectedBlocks(QVector<Block *> input, bool filter)
|
||||
|
||||
selected_blocks_.append(input);
|
||||
|
||||
emit SignalBlockSelectionChange();
|
||||
SignalBlockSelectionChange();
|
||||
}
|
||||
|
||||
void TimelineWidget::SignalDeselectedBlocks(const QVector<Block *> &deselected_blocks)
|
||||
@@ -1370,7 +1384,7 @@ void TimelineWidget::SignalDeselectedBlocks(const QVector<Block *> &deselected_b
|
||||
selected_blocks_.removeOne(b);
|
||||
}
|
||||
|
||||
emit SignalBlockSelectionChange();
|
||||
SignalBlockSelectionChange();
|
||||
}
|
||||
|
||||
void TimelineWidget::SignalDeselectedAllBlocks()
|
||||
|
||||
@@ -124,7 +124,7 @@ public:
|
||||
|
||||
void RestoreSplitterState(const QByteArray& state);
|
||||
|
||||
static void ReplaceBlocksWithGaps(const QVector<Block *> &blocks, bool remove_from_graph, MultiUndoCommand *command, bool handle_transitions = true);
|
||||
static void ReplaceBlocksWithGaps(const QVector<Block *> &blocks, bool remove_from_graph, MultiUndoCommand *command, bool handle_transitions = true, bool handle_invalidations = true);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the QGraphicsItem at a particular scene position
|
||||
@@ -237,7 +237,7 @@ public:
|
||||
|
||||
class SetSelectionsCommand : public UndoCommand {
|
||||
public:
|
||||
SetSelectionsCommand(TimelineWidget* timeline, const TimelineWidgetSelections& now, const TimelineWidgetSelections& old, bool process_block_changes) :
|
||||
SetSelectionsCommand(TimelineWidget* timeline, const TimelineWidgetSelections& now, const TimelineWidgetSelections& old, bool process_block_changes = true) :
|
||||
timeline_(timeline),
|
||||
old_(old),
|
||||
now_(now),
|
||||
@@ -329,6 +329,8 @@ private:
|
||||
|
||||
MultiUndoCommand *subtitle_show_command_;
|
||||
|
||||
QTimer *signal_block_change_timer_;
|
||||
|
||||
class SetSplitterSizesCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -38,6 +38,8 @@ set(OLIVE_SOURCES
|
||||
widget/timelinewidget/tool/slide.h
|
||||
widget/timelinewidget/tool/slip.cpp
|
||||
widget/timelinewidget/tool/slip.h
|
||||
widget/timelinewidget/tool/trackselect.cpp
|
||||
widget/timelinewidget/tool/trackselect.h
|
||||
widget/timelinewidget/tool/transition.cpp
|
||||
widget/timelinewidget/tool/transition.h
|
||||
widget/timelinewidget/tool/tool.cpp
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "node/factory.h"
|
||||
#include "node/generator/shape/shapenode.h"
|
||||
#include "node/generator/solid/solid.h"
|
||||
#include "node/generator/text/text.h"
|
||||
#include "node/generator/text/textv3.h"
|
||||
#include "widget/timelinewidget/timelinewidget.h"
|
||||
#include "widget/timelinewidget/undo/timelineundopointer.h"
|
||||
|
||||
@@ -138,7 +138,7 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
break;
|
||||
case olive::Tool::kAddableTitle:
|
||||
{
|
||||
node_to_add = new TextGenerator();
|
||||
node_to_add = new TextGeneratorV3();
|
||||
break;
|
||||
}
|
||||
case olive::Tool::kAddableBars:
|
||||
|
||||
@@ -44,6 +44,7 @@ PointerTool::PointerTool(TimelineWidget *parent) :
|
||||
trimming_allowed_(true),
|
||||
track_movement_allowed_(true),
|
||||
gap_trimming_allowed_(false),
|
||||
can_rubberband_select_(false),
|
||||
rubberband_selecting_(false)
|
||||
{
|
||||
}
|
||||
@@ -173,7 +174,7 @@ void PointerTool::MouseMove(TimelineViewMouseEvent *event)
|
||||
|
||||
// If we're performing an action, we can initiate ghosts
|
||||
if (drag_movement_mode_ != Timeline::kNone) {
|
||||
InitiateDrag(clicked_item_, drag_movement_mode_);
|
||||
InitiateDrag(clicked_item_, drag_movement_mode_, event->GetModifiers());
|
||||
}
|
||||
|
||||
// Set dragging to true here so no matter what, the drag isn't re-initiated until it's completed
|
||||
@@ -246,6 +247,7 @@ void SetGhostToSlideMode(TimelineViewGhostItem* g)
|
||||
|
||||
void PointerTool::InitiateDragInternal(Block *clicked_item,
|
||||
Timeline::MovementMode trim_mode,
|
||||
Qt::KeyboardModifiers modifiers,
|
||||
bool dont_roll_trims,
|
||||
bool allow_nongap_rolling,
|
||||
bool slide_instead_of_moving)
|
||||
@@ -409,30 +411,41 @@ void PointerTool::InitiateDragInternal(Block *clicked_item,
|
||||
}
|
||||
|
||||
Timeline::MovementMode flipped_mode = FlipTrimMode(trim_mode);
|
||||
TimelineViewGhostItem* adjacent_ghost;
|
||||
QVector<TimelineViewGhostItem*> adjacent_ghosts;
|
||||
|
||||
if (adjacent) {
|
||||
adjacent_ghost = AddGhostFromBlock(adjacent, flipped_mode);
|
||||
adjacent_ghosts.append(AddGhostFromBlock(adjacent, flipped_mode));
|
||||
|
||||
// Select adjacent's links if applicable
|
||||
// FIXME: The check for `clips.size() == 1` may not be necessary, but I don't know yet.
|
||||
// I'm only including it to prevent any potentially unintended behavior.
|
||||
if (clips.size() == 1 && !(modifiers & Qt::AltModifier)) {
|
||||
if (ClipBlock *adjacent_clip = dynamic_cast<ClipBlock*>(adjacent)) {
|
||||
foreach (Block *adjacent_link, adjacent_clip->block_links()) {
|
||||
adjacent_ghosts.append(AddGhostFromBlock(adjacent_link, flipped_mode));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (trim_mode == Timeline::kTrimIn || block->next()) {
|
||||
rational null_ghost_pos = (trim_mode == Timeline::kTrimIn) ? block->in() : block->out();
|
||||
|
||||
adjacent_ghost = AddGhostFromNull(null_ghost_pos, null_ghost_pos, clip_item->track()->ToReference(), flipped_mode);
|
||||
} else {
|
||||
adjacent_ghost = nullptr;
|
||||
adjacent_ghosts.append(AddGhostFromNull(null_ghost_pos, null_ghost_pos, clip_item->track()->ToReference(), flipped_mode));
|
||||
}
|
||||
|
||||
// If we have an adjacent block (for any reason), this is a roll edit and the adjacent is
|
||||
// expected to fill the remaining space (no gap needs to be created)
|
||||
ghost->SetData(TimelineViewGhostItem::kTrimIsARollEdit, static_cast<bool>(adjacent));
|
||||
|
||||
if (adjacent_ghost) {
|
||||
if (treat_trim_as_slide) {
|
||||
// We're sliding a transition rather than a pure trim/roll
|
||||
SetGhostToSlideMode(adjacent_ghost);
|
||||
} else if (dynamic_cast<GapBlock*>(block)) {
|
||||
ghost->SetData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
|
||||
} else {
|
||||
adjacent_ghost->SetData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
|
||||
foreach (TimelineViewGhostItem *adjacent_ghost, adjacent_ghosts) {
|
||||
if (adjacent_ghost) {
|
||||
if (treat_trim_as_slide) {
|
||||
// We're sliding a transition rather than a pure trim/roll
|
||||
SetGhostToSlideMode(adjacent_ghost);
|
||||
} else if (dynamic_cast<GapBlock*>(block)) {
|
||||
ghost->SetData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
|
||||
} else {
|
||||
adjacent_ghost->SetData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -560,6 +573,10 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
ghost->GetAdjustedLength(),
|
||||
ghost->GetMode());
|
||||
|
||||
if (event->GetModifiers() & Qt::ControlModifier) {
|
||||
|
||||
}
|
||||
|
||||
c->SetTrimIsARollEdit(ghost->GetData(TimelineViewGhostItem::kTrimIsARollEdit).toBool());
|
||||
|
||||
command->add_child(c);
|
||||
@@ -575,7 +592,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
} else {
|
||||
new_sel.TrimOut(reference_ghost->GetOutAdjustment());
|
||||
}
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), true));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,7 +641,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
TimelineWidgetSelections new_sel = parent()->GetSelections();
|
||||
new_sel.ShiftTime(blocks_moving.first().ghost->GetInAdjustment());
|
||||
new_sel.ShiftTracks(drag_track_type_, blocks_moving.first().ghost->GetTrackAdjustment());
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), true));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections()));
|
||||
}
|
||||
|
||||
if (!blocks_sliding.isEmpty()) {
|
||||
@@ -685,7 +702,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
// Adjust selections
|
||||
TimelineWidgetSelections new_sel = parent()->GetSelections();
|
||||
new_sel.ShiftTime(movement);
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), true));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,10 +731,9 @@ Timeline::MovementMode PointerTool::IsCursorInTrimHandle(Block *block, qreal cur
|
||||
}
|
||||
}
|
||||
|
||||
void PointerTool::InitiateDrag(Block *clicked_item,
|
||||
Timeline::MovementMode trim_mode)
|
||||
void PointerTool::InitiateDrag(Block *clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
InitiateDragInternal(clicked_item, trim_mode, false, false, false);
|
||||
InitiateDragInternal(clicked_item, trim_mode, modifiers, false, false, false);
|
||||
}
|
||||
|
||||
//#define HIDE_GAP_GHOSTS
|
||||
|
||||
@@ -39,8 +39,7 @@ public:
|
||||
protected:
|
||||
virtual void FinishDrag(TimelineViewMouseEvent *event);
|
||||
|
||||
virtual void InitiateDrag(Block* clicked_item,
|
||||
Timeline::MovementMode trim_mode);
|
||||
virtual void InitiateDrag(Block* clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers);
|
||||
|
||||
TimelineViewGhostItem* AddGhostFromBlock(Block *block, Timeline::MovementMode mode, bool check_if_exists = false);
|
||||
|
||||
@@ -66,13 +65,12 @@ protected:
|
||||
|
||||
void InitiateDragInternal(Block* clicked_item,
|
||||
Timeline::MovementMode trim_mode,
|
||||
Qt::KeyboardModifiers modifiers,
|
||||
bool dont_roll_trims,
|
||||
bool allow_nongap_rolling, bool slide_instead_of_moving);
|
||||
|
||||
const Timeline::MovementMode& drag_movement_mode() const
|
||||
{
|
||||
return drag_movement_mode_;
|
||||
}
|
||||
const Timeline::MovementMode& drag_movement_mode() const { return drag_movement_mode_; }
|
||||
void set_drag_movement_mode(const Timeline::MovementMode &d) { drag_movement_mode_ = d; }
|
||||
|
||||
void SetMovementAllowed(bool e)
|
||||
{
|
||||
@@ -94,6 +92,11 @@ protected:
|
||||
gap_trimming_allowed_ = e;
|
||||
}
|
||||
|
||||
void SetClickedItem(Block *b)
|
||||
{
|
||||
clicked_item_ = b;
|
||||
}
|
||||
|
||||
private:
|
||||
Timeline::MovementMode IsCursorInTrimHandle(Block* block, qreal cursor_x);
|
||||
|
||||
|
||||
@@ -34,10 +34,9 @@ RippleTool::RippleTool(TimelineWidget* parent) :
|
||||
SetGapTrimmingAllowed(true);
|
||||
}
|
||||
|
||||
void RippleTool::InitiateDrag(Block *clicked_item,
|
||||
Timeline::MovementMode trim_mode)
|
||||
void RippleTool::InitiateDrag(Block *clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
InitiateDragInternal(clicked_item, trim_mode, true, true, false);
|
||||
InitiateDragInternal(clicked_item, trim_mode, modifiers, true, true, false);
|
||||
|
||||
if (!parent()->HasGhosts()) {
|
||||
return;
|
||||
|
||||
@@ -32,8 +32,7 @@ public:
|
||||
protected:
|
||||
virtual void FinishDrag(TimelineViewMouseEvent *event) override;
|
||||
|
||||
virtual void InitiateDrag(Block* clicked_item,
|
||||
Timeline::MovementMode trim_mode) override;
|
||||
virtual void InitiateDrag(Block* clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -33,10 +33,9 @@ RollingTool::RollingTool(TimelineWidget* parent) :
|
||||
SetGapTrimmingAllowed(true);
|
||||
}
|
||||
|
||||
void RollingTool::InitiateDrag(Block *clicked_item,
|
||||
Timeline::MovementMode trim_mode)
|
||||
void RollingTool::InitiateDrag(Block *clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
InitiateDragInternal(clicked_item, trim_mode, false, true, false);
|
||||
InitiateDragInternal(clicked_item, trim_mode, modifiers, false, true, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ public:
|
||||
RollingTool(TimelineWidget* parent);
|
||||
|
||||
protected:
|
||||
virtual void InitiateDrag(Block* clicked_item,
|
||||
Timeline::MovementMode trim_mode) override;
|
||||
virtual void InitiateDrag(Block* clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@ SlideTool::SlideTool(TimelineWidget* parent) :
|
||||
SetGapTrimmingAllowed(true);
|
||||
}
|
||||
|
||||
void SlideTool::InitiateDrag(Block *clicked_item, Timeline::MovementMode trim_mode)
|
||||
void SlideTool::InitiateDrag(Block *clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
InitiateDragInternal(clicked_item, trim_mode, false, true, true);
|
||||
InitiateDragInternal(clicked_item, trim_mode, modifiers, false, true, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ public:
|
||||
SlideTool(TimelineWidget* parent);
|
||||
|
||||
protected:
|
||||
virtual void InitiateDrag(Block* clicked_item,
|
||||
Timeline::MovementMode trim_mode) override;
|
||||
virtual void InitiateDrag(Block* clicked_item, Timeline::MovementMode trim_mode, Qt::KeyboardModifiers modifiers) override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "trackselect.h"
|
||||
|
||||
#include "node/block/gap/gap.h"
|
||||
#include "node/output/track/track.h"
|
||||
#include "widget/timelinewidget/timelinewidget.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
TrackSelectTool::TrackSelectTool(TimelineWidget *parent) :
|
||||
PointerTool(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void TrackSelectTool::MousePress(TimelineViewMouseEvent *event)
|
||||
{
|
||||
QVector<Block*> blocks;
|
||||
bool forward = !(event->GetModifiers() & Qt::ControlModifier);
|
||||
|
||||
parent()->DeselectAll();
|
||||
|
||||
if (event->GetModifiers() & Qt::ShiftModifier) {
|
||||
// Track only
|
||||
Track *track = parent()->GetTrackFromReference(event->GetTrack());
|
||||
if (track) {
|
||||
SelectBlocksOnTrack(track, event, &blocks, forward);
|
||||
}
|
||||
} else {
|
||||
// All tracks
|
||||
foreach (Track *track, parent()->sequence()->GetTracks()) {
|
||||
SelectBlocksOnTrack(track, event, &blocks, forward);
|
||||
}
|
||||
}
|
||||
|
||||
if (!blocks.isEmpty()) {
|
||||
parent()->SignalSelectedBlocks(blocks);
|
||||
set_drag_movement_mode(Timeline::kMove);
|
||||
SetClickedItem(blocks.first());
|
||||
drag_start_ = event->GetCoordinates();
|
||||
} else {
|
||||
set_drag_movement_mode(Timeline::kNone);
|
||||
}
|
||||
}
|
||||
|
||||
void TrackSelectTool::SelectBlocksOnTrack(Track *track, TimelineViewMouseEvent *event, QVector<Block*> *blocks, bool forward)
|
||||
{
|
||||
Block *b = track->NearestBlockBeforeOrAt(event->GetFrame());
|
||||
|
||||
if (!b && !track->Blocks().isEmpty() && !forward) {
|
||||
// Fallback to first or last block in track
|
||||
b = track->Blocks().last();
|
||||
}
|
||||
|
||||
while (b) {
|
||||
if (!dynamic_cast<GapBlock*>(b)) {
|
||||
if (!blocks->contains(b)) {
|
||||
parent()->AddSelection(b);
|
||||
blocks->append(b);
|
||||
}
|
||||
|
||||
if (!(event->GetModifiers() & Qt::AltModifier)) {
|
||||
if (ClipBlock *clip = dynamic_cast<ClipBlock*>(b)) {
|
||||
foreach (Block *link, clip->block_links()) {
|
||||
if (!blocks->contains(link)) {
|
||||
parent()->AddSelection(link);
|
||||
blocks->append(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b = forward ? b->next() : b->previous();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef TRACKSELECTTOOL_H
|
||||
#define TRACKSELECTTOOL_H
|
||||
|
||||
#include "pointer.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class TrackSelectTool : public PointerTool
|
||||
{
|
||||
public:
|
||||
TrackSelectTool(TimelineWidget *parent);
|
||||
|
||||
virtual void MousePress(TimelineViewMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
void SelectBlocksOnTrack(Track *track, TimelineViewMouseEvent *event, QVector<Block *> *blocks, bool forward);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TRACKSELECTTOOL_H
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <QtMath>
|
||||
|
||||
#include "core.h"
|
||||
#include "ui/icons/icons.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "widget/timelinewidget/undo/timelineundogeneral.h"
|
||||
|
||||
@@ -56,15 +57,21 @@ TrackViewItem::TrackViewItem(Track* track, QWidget *parent) :
|
||||
connect(line_edit_, &FocusableLineEdit::Cancelled, this, &TrackViewItem::LineEditCancelled);
|
||||
stack_->addWidget(line_edit_);
|
||||
|
||||
mute_button_ = CreateMSLButton(tr("M"), Qt::red);
|
||||
mute_button_ = CreateMSLButton(Qt::red);
|
||||
mute_button_->setChecked(track->IsMuted());
|
||||
UpdateMuteButton(track->IsMuted());
|
||||
connect(mute_button_, &QPushButton::toggled, track_, &Track::SetMuted);
|
||||
connect(mute_button_, &QPushButton::toggled, this, &TrackViewItem::UpdateMuteButton);
|
||||
layout->addWidget(mute_button_);
|
||||
|
||||
/*solo_button_ = CreateMSLButton(tr("S"), Qt::yellow);
|
||||
layout->addWidget(solo_button_);*/
|
||||
|
||||
lock_button_ = CreateMSLButton(tr("L"), Qt::gray);
|
||||
lock_button_ = CreateMSLButton(Qt::gray);
|
||||
lock_button_->setChecked(track->IsLocked());
|
||||
UpdateLockButton(track->IsLocked());
|
||||
connect(lock_button_, &QPushButton::toggled, track_, &Track::SetLocked);
|
||||
connect(lock_button_, &QPushButton::toggled, this, &TrackViewItem::UpdateLockButton);
|
||||
layout->addWidget(lock_button_);
|
||||
|
||||
setMinimumHeight(mute_button_->height());
|
||||
@@ -74,9 +81,9 @@ TrackViewItem::TrackViewItem(Track* track, QWidget *parent) :
|
||||
connect(this, &QWidget::customContextMenuRequested, this, &TrackViewItem::ShowContextMenu);
|
||||
}
|
||||
|
||||
QPushButton *TrackViewItem::CreateMSLButton(const QString& text, const QColor& checked_color) const
|
||||
QPushButton *TrackViewItem::CreateMSLButton(const QColor& checked_color) const
|
||||
{
|
||||
QPushButton* button = new QPushButton(text);
|
||||
QPushButton* button = new QPushButton();
|
||||
button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
|
||||
button->setCheckable(true);
|
||||
button->setStyleSheet(QStringLiteral("QPushButton::checked { background: %1; }").arg(checked_color.name()));
|
||||
@@ -169,4 +176,14 @@ void TrackViewItem::DeleteAllEmptyTracks()
|
||||
}
|
||||
}
|
||||
|
||||
void TrackViewItem::UpdateMuteButton(bool e)
|
||||
{
|
||||
mute_button_->setIcon(e ? icon::EyeClosed : icon::EyeOpened);
|
||||
}
|
||||
|
||||
void TrackViewItem::UpdateLockButton(bool e)
|
||||
{
|
||||
lock_button_->setIcon(e ? icon::LockClosed : icon::LockOpened);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public:
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
QPushButton* CreateMSLButton(const QString &text, const QColor &checked_color) const;
|
||||
QPushButton* CreateMSLButton(const QColor &checked_color) const;
|
||||
|
||||
QStackedWidget* stack_;
|
||||
|
||||
@@ -67,6 +67,10 @@ private slots:
|
||||
|
||||
void DeleteAllEmptyTracks();
|
||||
|
||||
void UpdateMuteButton(bool e);
|
||||
|
||||
void UpdateLockButton(bool e);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -490,7 +490,9 @@ void TrackReplaceBlockWithGapCommand::redo()
|
||||
|
||||
track_->EndOperation();
|
||||
|
||||
track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput);
|
||||
if (handle_invalidations_) {
|
||||
track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Block is at the end of the track, simply remove it
|
||||
@@ -547,7 +549,9 @@ void TrackReplaceBlockWithGapCommand::undo()
|
||||
|
||||
track_->EndOperation();
|
||||
|
||||
track_->Node::InvalidateCache(TimeRange(block_->in(), block_->out()), Track::kBlockInput);
|
||||
if (handle_invalidations_) {
|
||||
track_->Node::InvalidateCache(TimeRange(block_->in(), block_->out()), Track::kBlockInput);
|
||||
}
|
||||
} else {
|
||||
|
||||
// Our gap and existing gap were both null, our block must have been at the end and thus
|
||||
|
||||
@@ -233,13 +233,14 @@ private:
|
||||
|
||||
class TrackReplaceBlockWithGapCommand : public UndoCommand {
|
||||
public:
|
||||
TrackReplaceBlockWithGapCommand(Track* track, Block* block, bool handle_transitions = true) :
|
||||
TrackReplaceBlockWithGapCommand(Track* track, Block* block, bool handle_transitions = true, bool handle_invalidations = true) :
|
||||
track_(track),
|
||||
block_(block),
|
||||
existing_gap_(nullptr),
|
||||
existing_merged_gap_(nullptr),
|
||||
our_gap_(nullptr),
|
||||
handle_transitions_(handle_transitions)
|
||||
handle_transitions_(handle_transitions),
|
||||
handle_invalidations_(handle_invalidations)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -265,6 +266,7 @@ private:
|
||||
GapBlock* our_gap_;
|
||||
|
||||
bool handle_transitions_;
|
||||
bool handle_invalidations_;
|
||||
|
||||
QObject memory_manager_;
|
||||
|
||||
@@ -360,6 +362,62 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class NodeBeginOperationCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeBeginOperationCommand(Node *node) :
|
||||
node_(node)
|
||||
{}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
{
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
node_->BeginOperation();
|
||||
}
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
node_->EndOperation();
|
||||
}
|
||||
|
||||
private:
|
||||
Node *node_;
|
||||
|
||||
};
|
||||
|
||||
class NodeEndOperationCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeEndOperationCommand(Node *node) :
|
||||
node_(node)
|
||||
{}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
{
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
node_->EndOperation();
|
||||
}
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
node_->BeginOperation();
|
||||
}
|
||||
|
||||
private:
|
||||
Node *node_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMELINEUNDOGENERAL_H
|
||||
|
||||
@@ -488,26 +488,36 @@ void TimelineRippleDeleteGapsAtRegionsCommand::prepare()
|
||||
}
|
||||
|
||||
max_gaps = qMax(max_gaps, gaps_on_track.size());
|
||||
} else {
|
||||
qWarning() << "Failed to find corresponding gap to region";
|
||||
}
|
||||
}
|
||||
|
||||
// For each gap on each track, find a corresponding gap on every other track (which may include
|
||||
// a requested gap) to ripple in order to keep everything synchronized
|
||||
QHash<GapBlock*, rational> gap_lengths;
|
||||
for (int gap_index=0; gap_index<max_gaps; gap_index++) {
|
||||
rational earliest_point = RATIONAL_MAX;
|
||||
rational ripple_length = RATIONAL_MAX;
|
||||
rational latest_point = RATIONAL_MIN;
|
||||
|
||||
foreach (const QVector<RemovalRequest> &gaps_on_track, requested_gaps) {
|
||||
if (gap_index < gaps_on_track.size()) {
|
||||
const RemovalRequest &gap = gaps_on_track.at(gap_index);
|
||||
earliest_point = qMin(earliest_point, gap.range.in());
|
||||
ripple_length = qMin(ripple_length, gap.range.length());
|
||||
latest_point = qMax(latest_point, gap.range.out());
|
||||
}
|
||||
}
|
||||
|
||||
// Determine which gaps will be involved in this operation
|
||||
QVector<GapBlock*> gaps;
|
||||
|
||||
bool all_tracks_unlocked = true;
|
||||
|
||||
foreach (Track* track, timeline_->GetTracks()) {
|
||||
if (track->IsLocked()) {
|
||||
all_tracks_unlocked = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -548,7 +558,12 @@ void TimelineRippleDeleteGapsAtRegionsCommand::prepare()
|
||||
|
||||
if (gap) {
|
||||
gaps.append(gap);
|
||||
ripple_length = qMin(ripple_length, gap->length());
|
||||
|
||||
if (!gap_lengths.contains(gap)) {
|
||||
gap_lengths.insert(gap, gap->length());
|
||||
}
|
||||
|
||||
ripple_length = qMin(ripple_length, gap_lengths.value(gap));
|
||||
}
|
||||
|
||||
if (ripple_length == 0) {
|
||||
@@ -558,11 +573,24 @@ void TimelineRippleDeleteGapsAtRegionsCommand::prepare()
|
||||
|
||||
if (ripple_length > 0) {
|
||||
foreach (GapBlock *gap, gaps) {
|
||||
if (gap->length() == ripple_length) {
|
||||
if (all_tracks_unlocked) {
|
||||
commands_.append(new NodeBeginOperationCommand(gap->track()));
|
||||
}
|
||||
|
||||
if (gap_lengths.value(gap) == ripple_length) {
|
||||
commands_.append(new TrackRippleRemoveBlockCommand(gap->track(), gap));
|
||||
} else {
|
||||
commands_.append(new BlockResizeCommand(gap, gap->length() - ripple_length));
|
||||
gap_lengths[gap] -= ripple_length;
|
||||
commands_.append(new BlockResizeCommand(gap, gap_lengths.value(gap)));
|
||||
}
|
||||
|
||||
if (all_tracks_unlocked) {
|
||||
commands_.append(new NodeEndOperationCommand(gap->track()));
|
||||
}
|
||||
}
|
||||
|
||||
if (all_tracks_unlocked) {
|
||||
commands_.append(new TimelineShiftCacheCommand(timeline_, latest_point, latest_point - ripple_length));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -582,4 +610,14 @@ void TimelineRippleDeleteGapsAtRegionsCommand::undo()
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineShiftCacheCommand::redo()
|
||||
{
|
||||
timeline_->ShiftCache(from_, to_);
|
||||
}
|
||||
|
||||
void TimelineShiftCacheCommand::undo()
|
||||
{
|
||||
timeline_->ShiftCache(to_, from_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ namespace olive {
|
||||
* 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 UndoCommand {
|
||||
class TrackRippleRemoveAreaCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
TrackRippleRemoveAreaCommand(Track* track, const TimeRange& range);
|
||||
|
||||
@@ -98,7 +99,8 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TrackListRippleRemoveAreaCommand : public UndoCommand {
|
||||
class TrackListRippleRemoveAreaCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
TrackListRippleRemoveAreaCommand(TrackList* list, rational in, rational out) :
|
||||
list_(list),
|
||||
@@ -134,7 +136,8 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TimelineRippleRemoveAreaCommand : public MultiUndoCommand {
|
||||
class TimelineRippleRemoveAreaCommand : public MultiUndoCommand
|
||||
{
|
||||
public:
|
||||
TimelineRippleRemoveAreaCommand(Sequence* timeline, rational in, rational out);
|
||||
|
||||
@@ -148,7 +151,8 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TrackListRippleToolCommand : public UndoCommand {
|
||||
class TrackListRippleToolCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
struct RippleInfo {
|
||||
Block* block;
|
||||
@@ -200,7 +204,8 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand {
|
||||
class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
TimelineRippleDeleteGapsAtRegionsCommand(Sequence* vo, const QVector<QPair<Track*, TimeRange> >& regions) :
|
||||
timeline_(vo),
|
||||
@@ -243,6 +248,34 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TimelineShiftCacheCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
TimelineShiftCacheCommand(Sequence* timeline, const rational &from, const rational &to) :
|
||||
timeline_(timeline),
|
||||
from_(from),
|
||||
to_(to)
|
||||
{}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
{
|
||||
return timeline_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Sequence* timeline_;
|
||||
|
||||
rational from_;
|
||||
|
||||
rational to_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMELINEUNDORIPPLE_H
|
||||
|
||||
@@ -38,14 +38,15 @@ void BlockSplitCommand::redo()
|
||||
if (!reconnect_tree_command_) {
|
||||
reconnect_tree_command_ = new MultiUndoCommand();
|
||||
new_block_ = static_cast<Block*>(Node::CopyNodeInGraph(block_, reconnect_tree_command_));
|
||||
if (ClipBlock *new_clip = dynamic_cast<ClipBlock*>(new_block_)) {
|
||||
ClipBlock *old_clip = static_cast<ClipBlock*>(block_);
|
||||
new_clip->waveform() = old_clip->waveform();
|
||||
}
|
||||
}
|
||||
|
||||
reconnect_tree_command_->redo_now();
|
||||
|
||||
if (ClipBlock *new_clip = dynamic_cast<ClipBlock*>(new_block_)) {
|
||||
ClipBlock *old_clip = static_cast<ClipBlock*>(block_);
|
||||
new_clip->waveform() = old_clip->waveform();
|
||||
}
|
||||
|
||||
// Determine our new lengths
|
||||
rational new_length = point_ - block_->in();
|
||||
rational new_part_length = block_->out() - point_;
|
||||
|
||||
@@ -360,6 +360,9 @@ void TimelineView::ToolChangedEvent(Tool::Item tool)
|
||||
case Tool::kZoom:
|
||||
setCursor(Qt::CrossCursor);
|
||||
break;
|
||||
case Tool::kTrackSelect:
|
||||
setCursor(Qt::SizeHorCursor); // FIXME: Not the ideal cursor
|
||||
break;
|
||||
default:
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user