Merge branch 'master' into marker
This commit is contained in:
@@ -28,7 +28,6 @@
|
||||
#include "core.h"
|
||||
#include "common/range.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "dialog/nodeproperties/nodepropertiesdialog.h"
|
||||
#include "dialog/sequence/sequence.h"
|
||||
#include "dialog/speedduration/speeddurationdialog.h"
|
||||
#include "node/block/transition/transition.h"
|
||||
@@ -301,55 +300,64 @@ void TimelineWidget::DisconnectNodeEvent(ViewerOutput *n)
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVector<Node *> &nodes, void* userdata)
|
||||
void TimelineWidget::CopyNodesToClipboardCallback(const QVector<Node *> &nodes, ProjectSerializer::SaveData *sdata, void *userdata)
|
||||
{
|
||||
// Cache the earliest in point so all copied clips have a "relative" in point that can be pasted anywhere
|
||||
QVector<Block*>& selected = *static_cast<QVector<Block*>*>(userdata);
|
||||
rational earliest_in = RATIONAL_MAX;
|
||||
ProjectSerializer::SerializedProperties properties;
|
||||
|
||||
foreach (Block* block, selected) {
|
||||
earliest_in = qMin(earliest_in, block->in());
|
||||
}
|
||||
|
||||
foreach (Block* block, selected) {
|
||||
writer->writeStartElement(QStringLiteral("block"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(block)));
|
||||
writer->writeAttribute(QStringLiteral("in"), (block->in() - earliest_in).toString());
|
||||
|
||||
Track* track = block->track();
|
||||
writer->writeAttribute(QStringLiteral("tracktype"), QString::number(track->type()));
|
||||
writer->writeAttribute(QStringLiteral("trackindex"), QString::number(track->Index()));
|
||||
|
||||
writer->writeEndElement();
|
||||
properties[block][QStringLiteral("in")] = (block->in() - earliest_in).toString();
|
||||
properties[block][QStringLiteral("track")] = block->track()->ToReference().ToString();
|
||||
}
|
||||
|
||||
sdata->SetProperties(properties);
|
||||
}
|
||||
|
||||
void TimelineWidget::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData& xml_node_data, void *userdata)
|
||||
void TimelineWidget::PasteNodesToClipboardCallback(const QVector<Node *> &nodes, const ProjectSerializer::LoadData &load_data, void *userdata)
|
||||
{
|
||||
QVector<BlockPasteData>& paste_data = *static_cast<QVector<BlockPasteData>*>(userdata);
|
||||
bool insert = *(bool*)userdata;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("block")) {
|
||||
BlockPasteData bpd;
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
|
||||
foreach (QXmlStreamAttribute attr, reader->attributes()) {
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
bpd.block = static_cast<Block*>(xml_node_data.node_ptrs.value(attr.value().toULongLong()));
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
bpd.in = rational::fromString(attr.value().toString());
|
||||
} else if (attr.name() == QStringLiteral("tracktype")) {
|
||||
bpd.track_type = static_cast<Track::Type>(attr.value().toInt());
|
||||
} else if (attr.name() == QStringLiteral("trackindex")) {
|
||||
bpd.track_index = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
foreach (Node *n, nodes) {
|
||||
command->add_child(new NodeAddCommand(GetConnectedNode()->project(), n));
|
||||
}
|
||||
|
||||
paste_data.append(bpd);
|
||||
rational paste_start = GetTime();
|
||||
|
||||
reader->skipCurrentElement();
|
||||
if (insert) {
|
||||
rational paste_end = GetTime();
|
||||
|
||||
for (auto it=load_data.properties.cbegin(); it!=load_data.properties.cend(); it++) {
|
||||
rational length = static_cast<Block*>(it.key())->length();
|
||||
rational in = rational::fromString(it.value()[QStringLiteral("in")]);
|
||||
|
||||
paste_end = qMax(paste_end, paste_start + in + length);
|
||||
}
|
||||
|
||||
if (paste_end != paste_start) {
|
||||
InsertGapsAt(paste_start, paste_end - paste_start, command);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it=load_data.properties.cbegin(); it!=load_data.properties.cend(); it++) {
|
||||
Block *block = static_cast<Block*>(it.key());
|
||||
rational in = rational::fromString(it.value()[QStringLiteral("in")]);
|
||||
Track::Reference track = Track::Reference::FromString(it.value()[QStringLiteral("track")]);
|
||||
|
||||
command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()),
|
||||
track.index(),
|
||||
block,
|
||||
paste_start + in));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
}
|
||||
|
||||
void TimelineWidget::SelectAll()
|
||||
@@ -500,8 +508,6 @@ void TimelineWidget::DeleteSelected(bool ripple)
|
||||
return;
|
||||
}
|
||||
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
QVector<Block*> clips_to_delete;
|
||||
QVector<TransitionBlock*> transitions_to_delete;
|
||||
|
||||
@@ -513,9 +519,16 @@ void TimelineWidget::DeleteSelected(bool ripple)
|
||||
}
|
||||
}
|
||||
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
// For transitions, remove them but extend their attached blocks to fill their place
|
||||
foreach (TransitionBlock* transition, transitions_to_delete) {
|
||||
command->add_child(new TransitionRemoveCommand(transition, true));
|
||||
TransitionRemoveCommand *trc = new TransitionRemoveCommand(transition, true);
|
||||
|
||||
// Perform the transition removal now so that replacing blocks with gaps below won't get confused
|
||||
trc->redo_now();
|
||||
|
||||
command->add_child(trc);
|
||||
}
|
||||
|
||||
// Replace clips with gaps (effectively deleting them)
|
||||
@@ -525,20 +538,28 @@ void TimelineWidget::DeleteSelected(bool ripple)
|
||||
command->add_child(new SetSelectionsCommand(this, TimelineWidgetSelections(), GetSelections(), false));
|
||||
|
||||
// Insert ripple command now that it's all cleaned up gaps
|
||||
TimelineRippleDeleteGapsAtRegionsCommand *ripple_command = nullptr;
|
||||
rational new_playhead = RATIONAL_MAX;
|
||||
if (ripple) {
|
||||
TimeRangeList range_list;
|
||||
QVector<QPair<Track*, TimeRange> > range_list;
|
||||
|
||||
foreach (Block* b, blocks_to_delete) {
|
||||
range_list.insert(TimeRange(b->in(), b->out()));
|
||||
range_list.append({b->track(), b->range()});
|
||||
new_playhead = qMin(new_playhead, b->in());
|
||||
}
|
||||
|
||||
command->add_child(new TimelineRippleDeleteGapsAtRegionsCommand(sequence(), range_list));
|
||||
ripple_command = new TimelineRippleDeleteGapsAtRegionsCommand(sequence(), range_list);
|
||||
command->add_child(ripple_command);
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
|
||||
// Ensures any current drag operations are cancelled
|
||||
ClearGhosts();
|
||||
|
||||
if (ripple_command && ripple_command->HasCommands() && new_playhead != RATIONAL_MAX) {
|
||||
SetTimeAndSignal(new_playhead);
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::IncreaseTrackHeight()
|
||||
@@ -651,34 +672,7 @@ void TimelineWidget::Paste(bool insert)
|
||||
return;
|
||||
}
|
||||
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
QVector<BlockPasteData> paste_data;
|
||||
QVector<Node*> pasted = PasteNodesFromClipboard(GetConnectedNode()->parent(), command, &paste_data);
|
||||
|
||||
rational paste_start = GetTime();
|
||||
|
||||
if (insert) {
|
||||
rational paste_end = GetTime();
|
||||
|
||||
foreach (const BlockPasteData& bpd, paste_data) {
|
||||
paste_end = qMax(paste_end, paste_start + bpd.in + bpd.block->length());
|
||||
}
|
||||
|
||||
if (paste_end != paste_start) {
|
||||
InsertGapsAt(paste_start, paste_end - paste_start, command);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const BlockPasteData& bpd, paste_data) {
|
||||
qDebug() << "Placing" << bpd.block;
|
||||
command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(bpd.track_type),
|
||||
bpd.track_index,
|
||||
bpd.block,
|
||||
paste_start + bpd.in));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
PasteNodesFromClipboard(&insert);
|
||||
}
|
||||
|
||||
void TimelineWidget::DeleteInToOut(bool ripple)
|
||||
@@ -944,6 +938,10 @@ void TimelineWidget::AddBlock(Block *block)
|
||||
connect(block, &Block::PreviewChanged, this, &TimelineWidget::BlockUpdated);
|
||||
|
||||
added_blocks_.append(block);
|
||||
|
||||
if (selections_[block->track()->ToReference()].contains(block->range()) && !selected_blocks_.contains(block)) {
|
||||
selected_blocks_.append(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1047,17 +1045,7 @@ void TimelineWidget::ShowContextMenu()
|
||||
menu.addSeparator();
|
||||
|
||||
QAction* properties_action = menu.addAction(tr("Properties"));
|
||||
connect(properties_action, &QAction::triggered, this, [this](){
|
||||
QVector<Block*> block_items = GetSelectedBlocks();
|
||||
QVector<Node*> nodes;
|
||||
|
||||
foreach (Block* i, block_items) {
|
||||
nodes.append(i);
|
||||
}
|
||||
|
||||
NodePropertiesDialog npd(nodes, timebase(), this);
|
||||
npd.exec();
|
||||
});
|
||||
connect(properties_action, &QAction::triggered, this, &TimelineWidget::ShowSpeedDurationDialogForSelectedClips);
|
||||
}
|
||||
|
||||
if (selected.isEmpty()) {
|
||||
@@ -1219,9 +1207,20 @@ void TimelineWidget::UpdateViewTimebases()
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::NudgeInternal(const rational &amount)
|
||||
void TimelineWidget::NudgeInternal(rational amount)
|
||||
{
|
||||
if (!selected_blocks_.isEmpty()) {
|
||||
// Validate
|
||||
foreach (Block* b, selected_blocks_) {
|
||||
if (b->in() + amount < 0) {
|
||||
amount = -b->in();
|
||||
}
|
||||
}
|
||||
|
||||
if (amount.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
|
||||
foreach (Block* b, selected_blocks_) {
|
||||
@@ -1392,7 +1391,7 @@ QVector<Timeline::EditToInfo> TimelineWidget::GetEditToInfo(const rational& play
|
||||
QVector<Timeline::EditToInfo> info_list(tracks.size());
|
||||
|
||||
for (int i=0;i<tracks.size();i++) {
|
||||
Timeline::EditToInfo info;
|
||||
Timeline::EditToInfo &info = info_list[i];
|
||||
|
||||
Track* track = tracks.at(i);
|
||||
info.track = track;
|
||||
@@ -1421,8 +1420,6 @@ QVector<Timeline::EditToInfo> TimelineWidget::GetEditToInfo(const rational& play
|
||||
}
|
||||
|
||||
info.nearest_block = b;
|
||||
|
||||
info_list[i] = info;
|
||||
}
|
||||
|
||||
return info_list;
|
||||
@@ -1439,7 +1436,7 @@ void TimelineWidget::RippleTo(Timeline::MovementMode mode)
|
||||
}
|
||||
|
||||
// Find each track's nearest point and determine the overall timeline's nearest point
|
||||
rational closest_point_to_playhead = (mode == Timeline::kTrimIn) ? 0 : RATIONAL_MAX;
|
||||
rational closest_point_to_playhead = (mode == Timeline::kTrimIn) ? RATIONAL_MIN : RATIONAL_MAX;
|
||||
|
||||
foreach (const Timeline::EditToInfo& info, tracks) {
|
||||
if (info.nearest_block) {
|
||||
@@ -1451,6 +1448,11 @@ void TimelineWidget::RippleTo(Timeline::MovementMode mode)
|
||||
}
|
||||
}
|
||||
|
||||
if (closest_point_to_playhead == RATIONAL_MIN || closest_point_to_playhead == RATIONAL_MAX) {
|
||||
// Assume no blocks will be acted upon
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're not inserting gaps and the edit point is right on the nearest in point, we enter a
|
||||
// single-frame mode where we remove one frame only
|
||||
if (closest_point_to_playhead == playhead_time) {
|
||||
@@ -1573,31 +1575,6 @@ QVector<Block *> TimelineWidget::GetBlocksInGlobalRect(const QPoint &p1, const Q
|
||||
return blocks_in_rect;
|
||||
}
|
||||
|
||||
QVector<Block *> TimelineWidget::GetBlocksInSelection(const TimelineWidgetSelections &sel)
|
||||
{
|
||||
QVector<Block *> blocks;
|
||||
|
||||
for (auto it=sel.cbegin(); it!=sel.cend(); it++) {
|
||||
const Track::Reference &ref = it.key();
|
||||
const TimeRangeList &list = it.value();
|
||||
|
||||
for (const TimeRange &r : list) {
|
||||
Track *track = GetTrackFromReference(ref);
|
||||
if (track) {
|
||||
for (Block *b : track->Blocks()) {
|
||||
if (r.Contains(b->range())) {
|
||||
blocks.append(b);
|
||||
} else if (b->in() >= r.out()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
void TimelineWidget::HideSnaps()
|
||||
{
|
||||
foreach (TimelineAndTrackView* tview, views_) {
|
||||
@@ -1722,8 +1699,31 @@ void TimelineWidget::SetSelections(const TimelineWidgetSelections &s, bool proce
|
||||
}
|
||||
|
||||
if (process_block_changes) {
|
||||
SignalDeselectedBlocks(GetBlocksInSelection(selections_.Subtracted(s)));
|
||||
SignalSelectedBlocks(GetBlocksInSelection(s.Subtracted(selections_)));
|
||||
QVector<Block*> deselected;
|
||||
QVector<Block*> selected;
|
||||
|
||||
foreach (Block *b, selected_blocks_) {
|
||||
if (!s[b->track()->ToReference()].contains(b->range())) {
|
||||
deselected.append(b);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This loop could do with some optimization
|
||||
for (auto it=s.cbegin(); it!=s.cend(); it++) {
|
||||
Track *track = GetTrackFromReference(it.key());
|
||||
if (track) {
|
||||
const TimeRangeList &ranges = it.value();
|
||||
|
||||
foreach (Block *b, track->Blocks()) {
|
||||
if (!selected_blocks_.contains(b) && ranges.contains(b->range())) {
|
||||
selected.append(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SignalDeselectedBlocks(deselected);
|
||||
SignalSelectedBlocks(selected);
|
||||
}
|
||||
|
||||
selections_ = s;
|
||||
|
||||
@@ -279,15 +279,8 @@ protected:
|
||||
virtual void ConnectNodeEvent(ViewerOutput* n) override;
|
||||
virtual void DisconnectNodeEvent(ViewerOutput* n) override;
|
||||
|
||||
virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVector<Node*> &nodes, void* userdata) override;
|
||||
virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void* userdata) override;
|
||||
|
||||
struct BlockPasteData {
|
||||
Block* block;
|
||||
rational in;
|
||||
Track::Type track_type;
|
||||
int track_index;
|
||||
};
|
||||
virtual void CopyNodesToClipboardCallback(const QVector<Node*> &nodes, ProjectSerializer::SaveData *data, void *userdata) override;
|
||||
virtual void PasteNodesToClipboardCallback(const QVector<Node*> &nodes, const ProjectSerializer::LoadData &load_data, void *userdata) override;
|
||||
|
||||
private:
|
||||
QVector<Timeline::EditToInfo> GetEditToInfo(const rational &playhead_time, Timeline::MovementMode mode);
|
||||
@@ -302,8 +295,6 @@ private:
|
||||
|
||||
QVector<Block*> GetBlocksInGlobalRect(const QPoint &p1, const QPoint &p2);
|
||||
|
||||
QVector<Block*> GetBlocksInSelection(const TimelineWidgetSelections &sel);
|
||||
|
||||
QPoint drag_origin_;
|
||||
|
||||
QRubberBand rubberband_;
|
||||
@@ -366,7 +357,7 @@ private:
|
||||
|
||||
void UpdateViewTimebases();
|
||||
|
||||
void NudgeInternal(const rational &amount);
|
||||
void NudgeInternal(rational amount);
|
||||
|
||||
void MoveToPlayheadInternal(bool out);
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
NodeGraph* graph = static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent());
|
||||
|
||||
command->add_child(new NodeAddCommand(graph, clip));
|
||||
command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0)));
|
||||
command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()),
|
||||
track.index(),
|
||||
clip,
|
||||
@@ -155,10 +155,10 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
}
|
||||
|
||||
if (node_to_add) {
|
||||
QPointF extra_node_offset(-1, 0);
|
||||
QPointF extra_node_offset(kDefaultDistanceFromOutput, 0);
|
||||
command->add_child(new NodeAddCommand(graph, node_to_add));
|
||||
command->add_child(new NodeEdgeAddCommand(node_to_add, NodeInput(clip, ClipBlock::kBufferIn)));
|
||||
command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset, false));
|
||||
command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
|
||||
@@ -355,7 +355,7 @@ void ImportTool::DropGhosts(bool insert)
|
||||
|
||||
command->add_child(new NodeAddCommand(dst_graph, new_sequence));
|
||||
command->add_child(new FolderAddChild(Core::instance()->GetSelectedFolderInActiveProject(), new_sequence));
|
||||
command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0)));
|
||||
new_sequence->add_default_nodes(command);
|
||||
|
||||
FootageToGhosts(0, dragged_footage_, new_sequence->GetVideoParams().time_base(), 0);
|
||||
@@ -394,10 +394,14 @@ void ImportTool::DropGhosts(bool insert)
|
||||
command->add_child(new NodeAddCommand(dst_graph, clip));
|
||||
|
||||
// Position clip in its own context
|
||||
command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0)));
|
||||
|
||||
int dep_pos = kDefaultDistanceFromOutput;
|
||||
|
||||
// Position footage in its context
|
||||
command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(-2, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(dep_pos, 0)));
|
||||
|
||||
dep_pos++;
|
||||
|
||||
switch (Track::Reference::TypeFromString(footage_stream.output)) {
|
||||
case Track::kVideo:
|
||||
@@ -409,7 +413,7 @@ void ImportTool::DropGhosts(bool insert)
|
||||
|
||||
command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(transform, TransformDistortNode::kTextureInput)));
|
||||
command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn)));
|
||||
command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(-1, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(dep_pos, 0)));
|
||||
break;
|
||||
}
|
||||
case Track::kAudio:
|
||||
@@ -421,7 +425,7 @@ void ImportTool::DropGhosts(bool insert)
|
||||
|
||||
command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(volume_node, VolumeNode::kSamplesInput)));
|
||||
command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn)));
|
||||
command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(-1, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(dep_pos, 0)));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -575,7 +575,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
} else {
|
||||
new_sel.TrimOut(reference_ghost->GetOutAdjustment());
|
||||
}
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), false));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,7 +624,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(), false));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), true));
|
||||
}
|
||||
|
||||
if (!blocks_sliding.isEmpty()) {
|
||||
@@ -685,7 +685,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(), false));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
const int TimelineTool::kDefaultDistanceFromOutput = -4;
|
||||
|
||||
TimelineTool::TimelineTool(TimelineWidget *parent) :
|
||||
dragging_(false),
|
||||
parent_(parent)
|
||||
|
||||
@@ -84,6 +84,8 @@ protected:
|
||||
|
||||
TimelineCoordinate drag_start_;
|
||||
|
||||
static const int kDefaultDistanceFromOutput;
|
||||
|
||||
private:
|
||||
TimelineWidget* parent_;
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
command->add_child(new NodeAddCommand(static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent()),
|
||||
transition));
|
||||
|
||||
command->add_child(new NodeSetPositionCommand(transition, transition, QPointF(0, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(transition, transition, QPointF(0, 0)));
|
||||
|
||||
command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()),
|
||||
track.index(),
|
||||
@@ -138,8 +138,8 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
command->add_child(new NodeEdgeAddCommand(in_block,
|
||||
NodeInput(transition, TransitionBlock::kInBlockInput)));
|
||||
|
||||
command->add_child(new NodeSetPositionCommand(out_block, transition, QPointF(-1, -0.5), false));
|
||||
command->add_child(new NodeSetPositionCommand(in_block, transition, QPointF(-1, 0.5), false));
|
||||
command->add_child(new NodeSetPositionCommand(out_block, transition, QPointF(-1, -0.5)));
|
||||
command->add_child(new NodeSetPositionCommand(in_block, transition, QPointF(-1, 0.5)));
|
||||
} else {
|
||||
Block* block_to_transition = Node::ValueToPtr<Block>(ghost_->GetData(TimelineViewGhostItem::kAttachedBlock));
|
||||
QString transition_input_to_connect;
|
||||
@@ -154,7 +154,7 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
command->add_child(new NodeEdgeAddCommand(block_to_transition,
|
||||
NodeInput(transition, transition_input_to_connect)));
|
||||
|
||||
command->add_child(new NodeSetPositionCommand(block_to_transition, transition, QPointF(-1, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(block_to_transition, transition, QPointF(-1, 0)));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
|
||||
@@ -22,10 +22,15 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
|
||||
#include "core.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "widget/timelinewidget/undo/timelineundogeneral.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
TrackViewItem::TrackViewItem(Track* track, QWidget *parent) :
|
||||
@@ -63,8 +68,10 @@ TrackViewItem::TrackViewItem(Track* track, QWidget *parent) :
|
||||
layout->addWidget(lock_button_);
|
||||
|
||||
setMinimumHeight(mute_button_->height());
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(track, &Track::MutedChanged, mute_button_, &QPushButton::setChecked);
|
||||
connect(this, &QWidget::customContextMenuRequested, this, &TrackViewItem::ShowContextMenu);
|
||||
}
|
||||
|
||||
QPushButton *TrackViewItem::CreateMSLButton(const QString& text, const QColor& checked_color) const
|
||||
@@ -114,4 +121,52 @@ void TrackViewItem::UpdateLabel()
|
||||
label_->setText(track_->GetLabelOrName());
|
||||
}
|
||||
|
||||
void TrackViewItem::ShowContextMenu(const QPoint &p)
|
||||
{
|
||||
Menu m(this);
|
||||
|
||||
QAction *delete_action = m.addAction(tr("&Delete"));
|
||||
connect(delete_action, &QAction::triggered, this, &TrackViewItem::DeleteTrack, Qt::QueuedConnection);
|
||||
|
||||
m.addSeparator();
|
||||
|
||||
QAction *delete_unused_action = m.addAction(tr("Delete All &Empty"));
|
||||
connect(delete_unused_action, &QAction::triggered, this, &TrackViewItem::DeleteAllEmptyTracks, Qt::QueuedConnection);
|
||||
|
||||
m.exec(mapToGlobal(p));
|
||||
}
|
||||
|
||||
void TrackViewItem::DeleteTrack()
|
||||
{
|
||||
Core::instance()->undo_stack()->push(new TimelineRemoveTrackCommand(track_));
|
||||
}
|
||||
|
||||
void TrackViewItem::DeleteAllEmptyTracks()
|
||||
{
|
||||
Sequence *sequence = track_->sequence();
|
||||
QVector<Track*> tracks_to_remove;
|
||||
QStringList track_names_to_remove;
|
||||
|
||||
foreach (Track *t, sequence->GetTracks()) {
|
||||
if (t->Blocks().isEmpty()) {
|
||||
tracks_to_remove.append(t);
|
||||
track_names_to_remove.append(t->GetLabelOrName());
|
||||
}
|
||||
}
|
||||
|
||||
if (tracks_to_remove.isEmpty()) {
|
||||
QMessageBox::information(this, tr("Delete All Empty"), tr("No tracks are currently empty"));
|
||||
} else {
|
||||
if (QMessageBox::question(this, tr("Delete All Empty"),
|
||||
tr("This will delete the following tracks:\n\n%1\n\nDo you wish to continue?").arg(track_names_to_remove.join('\n')),
|
||||
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
foreach (Track *track, tracks_to_remove) {
|
||||
command->add_child(new TimelineRemoveTrackCommand(track));
|
||||
}
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,6 +61,12 @@ private slots:
|
||||
|
||||
void UpdateLabel();
|
||||
|
||||
void ShowContextMenu(const QPoint &p);
|
||||
|
||||
void DeleteTrack();
|
||||
|
||||
void DeleteAllEmptyTracks();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ void TrackViewSplitter::SetHeightWithSizes(QList<int> sizes)
|
||||
if (alignment_ == Qt::AlignBottom) {
|
||||
sizes.replace(0, spacer_height_);
|
||||
} else {
|
||||
sizes.replace(count() - 1, spacer_height_);
|
||||
sizes.replace(sizes.size() - 1, spacer_height_);
|
||||
}
|
||||
|
||||
foreach (int s, sizes) {
|
||||
|
||||
@@ -73,131 +73,121 @@ void BlockSetMediaInCommand::undo()
|
||||
//
|
||||
// TimelineAddTrackCommand
|
||||
//
|
||||
TimelineAddTrackCommand::TimelineAddTrackCommand(TrackList *timeline, bool automerge_tracks)
|
||||
TimelineAddTrackCommand::TimelineAddTrackCommand(TrackList *timeline, bool automerge_tracks) :
|
||||
timeline_(timeline),
|
||||
merge_(nullptr),
|
||||
position_command_(nullptr)
|
||||
{
|
||||
timeline_ = timeline;
|
||||
position_command_ = nullptr;
|
||||
|
||||
// Create new track
|
||||
track_ = new Track();
|
||||
track_->setParent(&memory_manager_);
|
||||
|
||||
if (timeline->GetTrackCount() > 0 && automerge_tracks) {
|
||||
if (timeline_->type() == Track::kVideo) {
|
||||
merge_ = new MergeNode();
|
||||
base_ = NodeInput(merge_, MergeNode::kBaseIn);
|
||||
blend_ = NodeInput(merge_, MergeNode::kBlendIn);
|
||||
} else if (timeline_->type() == Track::kAudio) {
|
||||
merge_ = new MathNode();
|
||||
base_ = NodeInput(merge_, MathNode::kParamAIn);
|
||||
blend_ = NodeInput(merge_, MathNode::kParamBIn);
|
||||
} else {
|
||||
merge_ = nullptr;
|
||||
}
|
||||
} else {
|
||||
merge_ = nullptr;
|
||||
// Determine what input to connect it to
|
||||
QString relevant_input;
|
||||
|
||||
if (timeline_->type() == Track::kVideo) {
|
||||
relevant_input = Sequence::kTextureInput;
|
||||
} else if (timeline_->type() == Track::kAudio) {
|
||||
relevant_input = Sequence::kSamplesInput;
|
||||
}
|
||||
|
||||
if (merge_) {
|
||||
merge_->setParent(&memory_manager_);
|
||||
// If we have an input to connect to, set it as our `direct` connection
|
||||
if (!relevant_input.isEmpty()) {
|
||||
direct_ = NodeInput(timeline_->parent(), relevant_input);
|
||||
|
||||
// If we're automerging and something is already connected, determine if/how to merge it
|
||||
if (automerge_tracks && direct_.IsConnected()) {
|
||||
if (timeline_->type() == Track::kVideo) {
|
||||
// Use merge for video
|
||||
merge_ = new MergeNode();
|
||||
base_ = NodeInput(merge_, MergeNode::kBaseIn);
|
||||
blend_ = NodeInput(merge_, MergeNode::kBlendIn);
|
||||
} else if (timeline_->type() == Track::kAudio) {
|
||||
// Use math (add) for audio
|
||||
merge_ = new MathNode();
|
||||
base_ = NodeInput(merge_, MathNode::kParamAIn);
|
||||
blend_ = NodeInput(merge_, MathNode::kParamBIn);
|
||||
}
|
||||
|
||||
if (merge_) {
|
||||
// If we got created a merge node, ensure it's parented
|
||||
merge_->setParent(&memory_manager_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineAddTrackCommand::redo()
|
||||
{
|
||||
// Add track
|
||||
// Get sequence
|
||||
Sequence* sequence = timeline_->parent();
|
||||
|
||||
// Add track to sequence
|
||||
track_->setParent(timeline_->GetParentGraph());
|
||||
timeline_->ArrayAppend();
|
||||
Node::ConnectEdge(track_, timeline_->track_input(timeline_->ArraySize() - 1));
|
||||
|
||||
qreal position_factor = 0.5;
|
||||
if (timeline_->type() == Track::kVideo) {
|
||||
position_factor = -position_factor;
|
||||
}
|
||||
bool create_pos_command = (!position_command_ && (timeline_->type() == Track::kVideo || timeline_->type() == Track::kAudio));
|
||||
if (create_pos_command) {
|
||||
position_command_ = new MultiUndoCommand();
|
||||
}
|
||||
|
||||
// Add merge if applicable
|
||||
Track* last_track = nullptr;
|
||||
if (merge_) {
|
||||
// Determine what was previously connected
|
||||
Node *previous_connection = direct_.GetConnectedOutput();
|
||||
|
||||
// Add merge to graph
|
||||
merge_->setParent(timeline_->GetParentGraph());
|
||||
|
||||
last_track = timeline_->GetTrackAt(timeline_->GetTrackCount()-2);
|
||||
|
||||
// Whatever this track used to be connected to, connect the merge instead
|
||||
const Node::OutputConnections edges = last_track->output_connections();
|
||||
for (const Node::OutputConnection& ic : edges) {
|
||||
const NodeInput& i = ic.second;
|
||||
|
||||
// Ignore the track input, but funnel everything else through our merge
|
||||
if (i.node() != timeline_->parent() || i.input() != timeline_->track_input()) {
|
||||
Node::DisconnectEdge(last_track, i);
|
||||
Node::ConnectEdge(merge_, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Connect this as the "blend" track
|
||||
// Connect merge between what used to be here
|
||||
Node::DisconnectEdge(previous_connection, direct_);
|
||||
Node::ConnectEdge(merge_, direct_);
|
||||
Node::ConnectEdge(previous_connection, base_);
|
||||
Node::ConnectEdge(track_, blend_);
|
||||
Node::ConnectEdge(last_track, base_);
|
||||
} else if (timeline_->GetTrackCount() == 1) {
|
||||
// If this was the first track we added,
|
||||
QString relevant_input;
|
||||
|
||||
if (timeline_->type() == Track::kVideo) {
|
||||
relevant_input = ViewerOutput::kTextureInput;
|
||||
} else if (timeline_->type() == Track::kAudio) {
|
||||
relevant_input = ViewerOutput::kSamplesInput;
|
||||
if (create_pos_command) {
|
||||
position_command_->add_child(new NodeSetPositionCommand(track_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, -position_factor)));
|
||||
position_command_->add_child(new NodeSetPositionCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence)));
|
||||
position_command_->add_child(new NodeSetPositionAndDependenciesRecursivelyCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor * timeline_->GetTrackCount())));
|
||||
}
|
||||
} else if (direct_.IsValid() && !direct_.IsConnected()) {
|
||||
// If no merge, we have a direct connection, and nothing else is connected, connect this
|
||||
Node::ConnectEdge(track_, direct_);
|
||||
|
||||
if (!relevant_input.isEmpty() && !timeline_->parent()->IsInputConnected(relevant_input)) {
|
||||
direct_ = NodeInput(timeline_->parent(), relevant_input);
|
||||
|
||||
Node::ConnectEdge(track_, direct_);
|
||||
} else {
|
||||
direct_ = NodeInput();
|
||||
if (create_pos_command) {
|
||||
// Just position directly next to the context node
|
||||
position_command_->add_child(new NodeSetPositionCommand(track_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor)));
|
||||
}
|
||||
}
|
||||
|
||||
// Position track in context
|
||||
if (!position_command_) {
|
||||
int track_count = timeline_->parent()->GetTracks().size();
|
||||
position_command_ = new MultiUndoCommand();
|
||||
|
||||
// Position either the merge or the track as an "element"
|
||||
Node *node_to_position = merge_ ? merge_ : track_;
|
||||
double node_index = track_count - 1;
|
||||
if (merge_) {
|
||||
node_index -= 1;
|
||||
position_command_->add_child(new NodeRemovePositionFromContextCommand(last_track, timeline_->parent()));
|
||||
}
|
||||
|
||||
position_command_->add_child(new NodeSetPositionAsChildCommand(node_to_position, timeline_->parent(), timeline_->parent(), node_index, track_count, true));
|
||||
|
||||
// If we positioned a merge, position the tracks as children of the merge
|
||||
if (merge_) {
|
||||
// `last_track` should be non-null if `merge_` is non-null
|
||||
position_command_->add_child(new NodeSetPositionAsChildCommand(last_track, merge_, timeline_->parent(), 0, 2, true));
|
||||
position_command_->add_child(new NodeSetPositionAsChildCommand(track_, merge_, timeline_->parent(), 1, 2, true));
|
||||
}
|
||||
// Run position command if we created one
|
||||
if (position_command_) {
|
||||
position_command_->redo_now();
|
||||
}
|
||||
position_command_->redo_now();
|
||||
}
|
||||
|
||||
void TimelineAddTrackCommand::undo()
|
||||
{
|
||||
position_command_->undo_now();
|
||||
if (position_command_) {
|
||||
position_command_->undo_now();
|
||||
}
|
||||
|
||||
// Remove merge if applicable
|
||||
if (merge_) {
|
||||
// Assume whatever this merge is connected to USED to be connected to the last track
|
||||
Track* last_track = timeline_->GetTrackAt(timeline_->GetTrackCount()-2);
|
||||
Node *previous_connection = base_.GetConnectedOutput();
|
||||
|
||||
Node::DisconnectEdge(track_, blend_);
|
||||
Node::DisconnectEdge(last_track, base_);
|
||||
|
||||
// Make copy of edges since the node's internal array will change as we disconnect things
|
||||
const Node::OutputConnections edges = merge_->output_connections();
|
||||
for (const Node::OutputConnection& ic : edges) {
|
||||
const NodeInput& i = ic.second;
|
||||
|
||||
Node::DisconnectEdge(merge_, i);
|
||||
Node::ConnectEdge(last_track, i);
|
||||
}
|
||||
Node::DisconnectEdge(previous_connection, base_);
|
||||
Node::DisconnectEdge(merge_, direct_);
|
||||
Node::ConnectEdge(previous_connection, direct_);
|
||||
|
||||
merge_->setParent(&memory_manager_);
|
||||
} else if (direct_.IsValid()) {
|
||||
} else if (direct_.IsValid() && direct_.GetConnectedOutput() == track_) {
|
||||
Node::DisconnectEdge(track_, direct_);
|
||||
}
|
||||
|
||||
@@ -374,7 +364,7 @@ void TrackListInsertGaps::redo()
|
||||
}
|
||||
|
||||
if (split_command_) {
|
||||
split_command_->redo();
|
||||
split_command_->redo_now();
|
||||
}
|
||||
|
||||
foreach (auto add_gap, gaps_added_) {
|
||||
@@ -416,7 +406,7 @@ void TrackListInsertGaps::undo()
|
||||
|
||||
// Un-split blocks
|
||||
if (split_command_) {
|
||||
split_command_->undo();
|
||||
split_command_->undo_now();
|
||||
}
|
||||
|
||||
// Restore original length of gaps
|
||||
@@ -496,11 +486,6 @@ void TrackReplaceBlockWithGapCommand::redo()
|
||||
|
||||
our_gap_->setParent(track_->parent());
|
||||
track_->ReplaceBlock(block_, our_gap_);
|
||||
|
||||
if (!position_command_) {
|
||||
position_command_ = new NodeSetPositionAsChildCommand(our_gap_, track_, track_, our_gap_->index(), track_->Blocks().size(), true);
|
||||
}
|
||||
position_command_->redo_now();
|
||||
}
|
||||
|
||||
track_->EndOperation();
|
||||
@@ -533,8 +518,6 @@ void TrackReplaceBlockWithGapCommand::undo()
|
||||
track_->ReplaceBlock(our_gap_, block_);
|
||||
our_gap_->setParent(&memory_manager_);
|
||||
|
||||
position_command_->undo_now();
|
||||
|
||||
} else {
|
||||
|
||||
// If we're here, assume that we extended an existing gap
|
||||
@@ -608,4 +591,27 @@ void TrackReplaceBlockWithGapCommand::CreateRemoveTransitionCommandIfNecessary(b
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineRemoveTrackCommand::prepare()
|
||||
{
|
||||
list_ = track_->sequence()->track_list(track_->type());
|
||||
|
||||
index_ = list_->GetArrayIndexFromCacheIndex(track_->Index());
|
||||
|
||||
remove_command_ = new NodeRemoveWithExclusiveDependenciesAndDisconnect(track_);
|
||||
}
|
||||
|
||||
void TimelineRemoveTrackCommand::redo()
|
||||
{
|
||||
remove_command_->redo_now();
|
||||
|
||||
list_->parent()->InputArrayRemove(list_->track_input(), index_);
|
||||
}
|
||||
|
||||
void TimelineRemoveTrackCommand::undo()
|
||||
{
|
||||
list_->parent()->InputArrayInsert(list_->track_input(), index_);
|
||||
|
||||
remove_command_->undo_now();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -163,6 +163,42 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TimelineRemoveTrackCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
TimelineRemoveTrackCommand(Track *track) :
|
||||
track_(track),
|
||||
remove_command_(nullptr)
|
||||
{}
|
||||
|
||||
virtual ~TimelineRemoveTrackCommand()
|
||||
{
|
||||
delete remove_command_;
|
||||
}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
{
|
||||
return track_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void prepare() override;
|
||||
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Track *track_;
|
||||
|
||||
TrackList *list_;
|
||||
|
||||
int index_;
|
||||
|
||||
UndoCommand *remove_command_;
|
||||
|
||||
};
|
||||
|
||||
class TransitionRemoveCommand : public UndoCommand {
|
||||
public:
|
||||
TransitionRemoveCommand(TransitionBlock* block, bool remove_from_graph) :
|
||||
@@ -203,16 +239,10 @@ public:
|
||||
existing_gap_(nullptr),
|
||||
existing_merged_gap_(nullptr),
|
||||
our_gap_(nullptr),
|
||||
handle_transitions_(handle_transitions),
|
||||
position_command_(nullptr)
|
||||
handle_transitions_(handle_transitions)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~TrackReplaceBlockWithGapCommand() override
|
||||
{
|
||||
delete position_command_;
|
||||
}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
{
|
||||
return block_->project();
|
||||
@@ -236,8 +266,6 @@ private:
|
||||
|
||||
bool handle_transitions_;
|
||||
|
||||
NodeSetPositionAsChildCommand* position_command_;
|
||||
|
||||
QObject memory_manager_;
|
||||
|
||||
QVector<TransitionRemoveCommand*> transition_remove_commands_;
|
||||
|
||||
@@ -324,7 +324,6 @@ TrackPlaceBlockCommand::~TrackPlaceBlockCommand()
|
||||
{
|
||||
delete ripple_remove_command_;
|
||||
qDeleteAll(add_track_commands_);
|
||||
qDeleteAll(position_commands_);
|
||||
}
|
||||
|
||||
void TrackPlaceBlockCommand::redo()
|
||||
@@ -367,14 +366,6 @@ void TrackPlaceBlockCommand::redo()
|
||||
}
|
||||
|
||||
track->AppendBlock(insert_);
|
||||
|
||||
if (position_commands_.isEmpty()) {
|
||||
// Create position commands for insert and gap if necessary
|
||||
if (gap_) {
|
||||
position_commands_.append(new NodeSetPositionAsChildCommand(gap_, track, track, gap_->index(), track->Blocks().size(), true));
|
||||
}
|
||||
position_commands_.append(new NodeSetPositionAsChildCommand(insert_, track, track, insert_->index(), track->Blocks().size(), true));
|
||||
}
|
||||
} else {
|
||||
// Place the Block at this point
|
||||
if (!ripple_remove_command_) {
|
||||
@@ -384,10 +375,6 @@ void TrackPlaceBlockCommand::redo()
|
||||
|
||||
ripple_remove_command_->redo_now();
|
||||
track->InsertBlockAfter(insert_, ripple_remove_command_->GetInsertionIndex());
|
||||
|
||||
if (position_commands_.isEmpty()) {
|
||||
position_commands_.append(new NodeSetPositionAsChildCommand(insert_, track, track, insert_->index(), track->Blocks().size(), true));
|
||||
}
|
||||
}
|
||||
|
||||
track->EndOperation();
|
||||
@@ -397,18 +384,10 @@ void TrackPlaceBlockCommand::redo()
|
||||
foreach (const TimeRange &r, ranges_to_invalidate) {
|
||||
track->Node::InvalidateCache(r, Track::kBlockInput);
|
||||
}
|
||||
|
||||
for (int i=0; i<position_commands_.size(); i++) {
|
||||
position_commands_.at(i)->redo_now();
|
||||
}
|
||||
}
|
||||
|
||||
void TrackPlaceBlockCommand::undo()
|
||||
{
|
||||
for (int i=position_commands_.size()-1; i>=0; i--) {
|
||||
position_commands_.at(i)->undo_now();
|
||||
}
|
||||
|
||||
Track* t = timeline_->GetTrackAt(track_index_);
|
||||
|
||||
TimeRange insert_range(insert_->in(), insert_->out());
|
||||
|
||||
@@ -198,7 +198,6 @@ private:
|
||||
QVector<TimelineAddTrackCommand*> add_track_commands_;
|
||||
QObject memory_manager_;
|
||||
TrackRippleRemoveAreaCommand* ripple_remove_command_;
|
||||
QVector<NodeSetPositionAsChildCommand*> position_commands_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ void TrackRippleRemoveAreaCommand::redo()
|
||||
|
||||
if (splice_split_command_) {
|
||||
// We're just splicing
|
||||
splice_split_command_->redo();
|
||||
splice_split_command_->redo_now();
|
||||
|
||||
// Trim the in of the split
|
||||
Block* split = splice_split_command_->new_block();
|
||||
@@ -157,7 +157,7 @@ void TrackRippleRemoveAreaCommand::undo()
|
||||
track_->BeginOperation();
|
||||
|
||||
if (splice_split_command_) {
|
||||
splice_split_command_->undo();
|
||||
splice_split_command_->undo_now();
|
||||
} else {
|
||||
if (trim_out_.block) {
|
||||
trim_out_.block->set_length_and_media_out(trim_out_.old_length);
|
||||
@@ -457,46 +457,128 @@ void TrackListRippleToolCommand::ripple(bool redo)
|
||||
//
|
||||
// TimelineRippleDeleteGapsAtRegionsCommand
|
||||
//
|
||||
void TimelineRippleDeleteGapsAtRegionsCommand::redo()
|
||||
void TimelineRippleDeleteGapsAtRegionsCommand::prepare()
|
||||
{
|
||||
if (commands_.isEmpty()) {
|
||||
foreach (const TimeRange& range, regions_) {
|
||||
rational max_ripple_length = range.length();
|
||||
int max_gaps = 0;
|
||||
QHash<Track*, QVector<RemovalRequest> > requested_gaps;
|
||||
|
||||
QVector<Block*> blocks_around_range;
|
||||
// Convert regions to gaps
|
||||
for (const QPair<Track*, TimeRange> ®ion : qAsConst(regions_)) {
|
||||
Track *track = region.first;
|
||||
const TimeRange &range = region.second;
|
||||
|
||||
foreach (Track* track, timeline_->GetTracks()) {
|
||||
// Get the block from every other track that is either at or just before our block's in point
|
||||
Block* block_at_time = track->NearestBlockBeforeOrAt(range.in());
|
||||
GapBlock *gap = dynamic_cast<GapBlock*>(track->NearestBlockBeforeOrAt(range.in()));
|
||||
|
||||
if (block_at_time) {
|
||||
if (dynamic_cast<GapBlock*>(block_at_time)) {
|
||||
max_ripple_length = qMin(block_at_time->length(), max_ripple_length);
|
||||
} else {
|
||||
max_ripple_length = 0;
|
||||
break;
|
||||
if (gap) {
|
||||
QVector<RemovalRequest> &gaps_on_track = requested_gaps[track];
|
||||
|
||||
RemovalRequest this_req = {gap, range};
|
||||
|
||||
// Insertion sort
|
||||
bool inserted = false;
|
||||
for (int i=0; i<gaps_on_track.size(); i++) {
|
||||
if (gaps_on_track.at(i).range.in() < range.in()) {
|
||||
gaps_on_track.insert(i, this_req);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inserted) {
|
||||
gaps_on_track.append(this_req);
|
||||
}
|
||||
|
||||
max_gaps = qMax(max_gaps, gaps_on_track.size());
|
||||
}
|
||||
}
|
||||
|
||||
for (int gap_index=0; gap_index<max_gaps; gap_index++) {
|
||||
rational earliest_point = RATIONAL_MAX;
|
||||
rational ripple_length = RATIONAL_MAX;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// Determine which gaps will be involved in this operation
|
||||
QVector<GapBlock*> gaps;
|
||||
|
||||
foreach (Track* track, timeline_->GetTracks()) {
|
||||
if (track->IsLocked()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QVector<RemovalRequest> &requested_gaps_on_track = requested_gaps.value(track);
|
||||
GapBlock *gap = nullptr;
|
||||
if (gap_index < requested_gaps_on_track.size()) {
|
||||
// A requested gap was at this index, use it
|
||||
gap = requested_gaps_on_track.at(gap_index).gap;
|
||||
} else {
|
||||
// No requested gap was at this index, find one
|
||||
Block *block = track->NearestBlockAfterOrAt(earliest_point);
|
||||
|
||||
if (block) {
|
||||
// Found a block, test if it's a gap
|
||||
gap = dynamic_cast<GapBlock*>(block);
|
||||
|
||||
if (!gap) {
|
||||
if (block->in() == earliest_point) {
|
||||
if (block->next()) {
|
||||
gap = dynamic_cast<GapBlock*>(block->next());
|
||||
|
||||
if (!gap) {
|
||||
ripple_length = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gap = dynamic_cast<GapBlock*>(block->previous());
|
||||
|
||||
if (!gap) {
|
||||
ripple_length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blocks_around_range.append(block_at_time);
|
||||
} else {
|
||||
// Assume track finishes here and track won't be affected by this operation
|
||||
}
|
||||
}
|
||||
|
||||
if (max_ripple_length > 0) {
|
||||
foreach (Block* resize, blocks_around_range) {
|
||||
if (resize->length() == max_ripple_length) {
|
||||
// Remove block entirely
|
||||
commands_.append(new TrackRippleRemoveBlockCommand(resize->track(), resize));
|
||||
} else {
|
||||
// Resize block
|
||||
commands_.append(new BlockResizeCommand(resize, resize->length() - max_ripple_length));
|
||||
}
|
||||
if (gap) {
|
||||
gaps.append(gap);
|
||||
ripple_length = qMin(ripple_length, gap->length());
|
||||
}
|
||||
|
||||
if (ripple_length == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ripple_length > 0) {
|
||||
foreach (GapBlock *gap, gaps) {
|
||||
if (gap->length() == ripple_length) {
|
||||
commands_.append(new TrackRippleRemoveBlockCommand(gap->track(), gap));
|
||||
} else {
|
||||
commands_.append(new BlockResizeCommand(gap, gap->length() - ripple_length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (UndoCommand* c, commands_) {
|
||||
c->redo_now();
|
||||
void TimelineRippleDeleteGapsAtRegionsCommand::redo()
|
||||
{
|
||||
for (auto it=commands_.cbegin(); it!=commands_.cend(); it++) {
|
||||
(*it)->redo_now();
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineRippleDeleteGapsAtRegionsCommand::undo()
|
||||
{
|
||||
for (auto it=commands_.crbegin(); it!=commands_.crend(); it++) {
|
||||
(*it)->undo_now();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ private:
|
||||
|
||||
class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand {
|
||||
public:
|
||||
TimelineRippleDeleteGapsAtRegionsCommand(Sequence* vo, const TimeRangeList& regions) :
|
||||
TimelineRippleDeleteGapsAtRegionsCommand(Sequence* vo, const QVector<QPair<Track*, TimeRange> >& regions) :
|
||||
timeline_(vo),
|
||||
regions_(regions)
|
||||
{
|
||||
@@ -218,22 +218,29 @@ public:
|
||||
return timeline_->project();
|
||||
}
|
||||
|
||||
bool HasCommands() const
|
||||
{
|
||||
return !commands_.isEmpty();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void prepare() override;
|
||||
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
for (int i=commands_.size()-1;i>=0;i--) {
|
||||
commands_.at(i)->undo_now();
|
||||
}
|
||||
}
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Sequence* timeline_;
|
||||
TimeRangeList regions_;
|
||||
QVector<QPair<Track*, TimeRange> > regions_;
|
||||
|
||||
QVector<UndoCommand*> commands_;
|
||||
|
||||
struct RemovalRequest {
|
||||
GapBlock *gap;
|
||||
TimeRange range;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -61,12 +61,6 @@ void BlockSplitCommand::redo()
|
||||
// Insert new block
|
||||
track->InsertBlockAfter(new_block(), block_);
|
||||
|
||||
// Position the block
|
||||
if (!position_command_) {
|
||||
position_command_ = new NodeSetPositionAsChildCommand(new_block(), track, track, new_block()->index(), track->Blocks().size(), true);
|
||||
}
|
||||
position_command_->redo_now();
|
||||
|
||||
// If the block had an out transition, we move it to the new block
|
||||
moved_transition_ = NodeInput();
|
||||
|
||||
@@ -96,8 +90,6 @@ void BlockSplitCommand::undo()
|
||||
Node::ConnectEdge(block_, moved_transition_);
|
||||
}
|
||||
|
||||
position_command_->undo_now();
|
||||
|
||||
block_->set_length_and_media_out(old_length_);
|
||||
track->RippleRemoveBlock(new_block());
|
||||
|
||||
@@ -130,7 +122,7 @@ void BlockSplitPreservingLinksCommand::redo()
|
||||
|
||||
if (b->in() < time && b->out() > time) {
|
||||
BlockSplitCommand* split_command = new BlockSplitCommand(b, time);
|
||||
split_command->redo();
|
||||
split_command->redo_now();
|
||||
splits.replace(j, split_command->new_block());
|
||||
commands_.append(split_command);
|
||||
} else {
|
||||
|
||||
@@ -31,15 +31,13 @@ public:
|
||||
block_(block),
|
||||
new_block_(nullptr),
|
||||
point_(point),
|
||||
reconnect_tree_command_(nullptr),
|
||||
position_command_(nullptr)
|
||||
reconnect_tree_command_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~BlockSplitCommand() override
|
||||
{
|
||||
delete reconnect_tree_command_;
|
||||
delete position_command_;
|
||||
}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
@@ -55,6 +53,7 @@ public:
|
||||
return new_block_;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
@@ -70,8 +69,6 @@ private:
|
||||
|
||||
NodeInput moved_transition_;
|
||||
|
||||
NodeSetPositionAsChildCommand* position_command_;
|
||||
|
||||
};
|
||||
|
||||
class BlockSplitPreservingLinksCommand : public UndoCommand {
|
||||
@@ -92,6 +89,7 @@ public:
|
||||
return blocks_.first()->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override
|
||||
@@ -129,6 +127,7 @@ public:
|
||||
return track_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void prepare() override;
|
||||
|
||||
virtual void redo() override
|
||||
|
||||
Reference in New Issue
Block a user