Merge branch 'master' into marker

This commit is contained in:
ThomasWilshaw
2022-01-28 22:30:29 +00:00
committed by GitHub
307 changed files with 26841 additions and 11362 deletions
+107 -107
View File
@@ -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;
+3 -12
View File
@@ -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);
+3 -3
View File
@@ -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);
+9 -5
View File
@@ -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:
+3 -3
View File
@@ -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));
}
}
+2
View File
@@ -24,6 +24,8 @@
namespace olive {
const int TimelineTool::kDefaultDistanceFromOutput = -4;
TimelineTool::TimelineTool(TimelineWidget *parent) :
dragging_(false),
parent_(parent)
+2
View File
@@ -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> &region : 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