timeline: implement nest function
Also includes some improved cache/request code
This commit is contained in:
+2
-2
@@ -1490,7 +1490,7 @@ bool Core::LabelNodes(const QVector<Node *> &nodes, MultiUndoCommand *parent)
|
||||
return false;
|
||||
}
|
||||
|
||||
Sequence *Core::CreateNewSequenceForProject(Project* project) const
|
||||
Sequence *Core::CreateNewSequenceForProject(const QString &format, Project* project)
|
||||
{
|
||||
Sequence* new_sequence = new Sequence();
|
||||
|
||||
@@ -1498,7 +1498,7 @@ Sequence *Core::CreateNewSequenceForProject(Project* project) const
|
||||
int sequence_number = 1;
|
||||
QString sequence_name;
|
||||
do {
|
||||
sequence_name = tr("Sequence %1").arg(sequence_number);
|
||||
sequence_name = format.arg(sequence_number);
|
||||
sequence_number++;
|
||||
} while (project->root()->ChildExistsWithName(sequence_name));
|
||||
new_sequence->SetLabel(sequence_name);
|
||||
|
||||
+5
-1
@@ -255,7 +255,11 @@ public:
|
||||
/**
|
||||
* @brief Create a new sequence named appropriately for the active project
|
||||
*/
|
||||
Sequence* CreateNewSequenceForProject(Project *project) const;
|
||||
static Sequence* CreateNewSequenceForProject(const QString &format, Project *project);
|
||||
static Sequence* CreateNewSequenceForProject(Project *project)
|
||||
{
|
||||
return CreateNewSequenceForProject(tr("Sequence %1"), project);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Opens a project from the recently opened list
|
||||
|
||||
@@ -232,7 +232,7 @@ void ClipBlock::RequestRangeFromConnected(const TimeRange &range)
|
||||
{
|
||||
TimeRange thumb_range = range.Intersected(max_range);
|
||||
if (GetAdjustedThumbnailRange(&thumb_range)) {
|
||||
emit connected->thumbnail_cache()->Request(thumb_range);
|
||||
connected->thumbnail_cache()->Request(thumb_range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ void ClipBlock::RequestRangeForCache(PlaybackCache *cache, const TimeRange &max_
|
||||
}
|
||||
|
||||
if (request) {
|
||||
emit cache->Request(r);
|
||||
cache->Request(r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -404,6 +404,33 @@ QVector<Block *> Track::BlocksAtTimeRange(const TimeRange &range) const
|
||||
return list;
|
||||
}
|
||||
|
||||
bool Track::IsRangeFree(const TimeRange &range) const
|
||||
{
|
||||
Block *b = NearestBlockBeforeOrAt(range.in());
|
||||
if (!b) {
|
||||
// No block here, assume track is empty here
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!dynamic_cast<GapBlock*>(b)) {
|
||||
// There's a block at or around the start point that isn't a gap, range is not free
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((b = b->next())) {
|
||||
if (b->in() >= range.out()) {
|
||||
// This block is after the range, no longer relevant
|
||||
break;
|
||||
} else if (!dynamic_cast<GapBlock*>(b)) {
|
||||
// Found a block in this range, range is not free
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, we couldn't find anything in the way of this range
|
||||
return true;
|
||||
}
|
||||
|
||||
void Track::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options)
|
||||
{
|
||||
TimeRange limited;
|
||||
|
||||
@@ -338,6 +338,11 @@ public:
|
||||
*/
|
||||
QVector<Block*> BlocksAtTimeRange(const TimeRange& range) const;
|
||||
|
||||
/*
|
||||
* @brief Returns whether a time range is empty or only has a gap
|
||||
*/
|
||||
bool IsRangeFree(const TimeRange &range) const;
|
||||
|
||||
const QVector<Block *> &Blocks() const
|
||||
{
|
||||
return blocks_;
|
||||
|
||||
@@ -224,16 +224,16 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from,
|
||||
|
||||
if (Node *connected = GetConnectedOutput(from, element)) {
|
||||
if (from == kTextureInput) {
|
||||
//emit connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly);
|
||||
//connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly);
|
||||
if (autocache_input_video_) {
|
||||
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetVideoLength()));
|
||||
emit connected->video_frame_cache()->Request(range.Intersected(max_range));
|
||||
connected->video_frame_cache()->Request(range.Intersected(max_range));
|
||||
}
|
||||
} else if (from == kSamplesInput) {
|
||||
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength()));
|
||||
emit connected->waveform_cache()->Request(range.Intersected(max_range));
|
||||
connected->waveform_cache()->Request(range.Intersected(max_range));
|
||||
if (autocache_input_audio_) {
|
||||
emit connected->audio_playback_cache()->Request(range.Intersected(max_range));
|
||||
connected->audio_playback_cache()->Request(range.Intersected(max_range));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,7 +393,7 @@ void ViewerOutput::ConnectedToPreviewEvent()
|
||||
TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength()));
|
||||
TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range);
|
||||
for (const TimeRange &r : invalid) {
|
||||
emit connected->waveform_cache()->Request(r);
|
||||
connected->waveform_cache()->Request(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,11 @@ public:
|
||||
timeline_widget()->ShowSpeedDurationDialogForSelectedClips();
|
||||
}
|
||||
|
||||
void NestSelectedClips()
|
||||
{
|
||||
timeline_widget()->NestSelectedClips();
|
||||
}
|
||||
|
||||
void InsertFootageAtPlayhead(const QVector<ViewerOutput *> &footage);
|
||||
|
||||
void OverwriteFootageAtPlayhead(const QVector<ViewerOutput *> &footage);
|
||||
|
||||
@@ -223,6 +223,13 @@ void PlaybackCache::InvalidateAll()
|
||||
Invalidate(TimeRange(0, RATIONAL_MAX));
|
||||
}
|
||||
|
||||
void PlaybackCache::Request(const TimeRange &r)
|
||||
{
|
||||
requested_.insert(r);
|
||||
|
||||
emit Requested(r);
|
||||
}
|
||||
|
||||
void PlaybackCache::Validate(const TimeRange &r, bool signal)
|
||||
{
|
||||
validated_.insert(r);
|
||||
|
||||
@@ -98,15 +98,29 @@ public:
|
||||
|
||||
const QVector<Passthrough> &GetPassthroughs() const { return passthroughs_; }
|
||||
|
||||
void ClearRequestRange(const olive::TimeRange &r)
|
||||
{
|
||||
requested_.remove(r);
|
||||
}
|
||||
|
||||
void ResignalRequests()
|
||||
{
|
||||
for (const TimeRange &r : requested_) {
|
||||
emit Requested(r);
|
||||
}
|
||||
}
|
||||
|
||||
public slots:
|
||||
void InvalidateAll();
|
||||
|
||||
void Request(const olive::TimeRange &r);
|
||||
|
||||
signals:
|
||||
void Invalidated(const olive::TimeRange& r);
|
||||
|
||||
void Validated(const olive::TimeRange& r);
|
||||
|
||||
void Request(const olive::TimeRange& r);
|
||||
void Requested(const olive::TimeRange& r);
|
||||
|
||||
void CancelAll();
|
||||
|
||||
@@ -124,6 +138,8 @@ protected:
|
||||
private:
|
||||
TimeRangeList validated_;
|
||||
|
||||
TimeRangeList requested_;
|
||||
|
||||
QUuid uuid_;
|
||||
|
||||
bool saving_enabled_;
|
||||
|
||||
@@ -100,6 +100,8 @@ void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range)
|
||||
{
|
||||
PlaybackCache *cache = static_cast<PlaybackCache*>(sender());
|
||||
|
||||
cache->ClearRequestRange(range);
|
||||
|
||||
VideoInvalidatedFromNode(cache, range);
|
||||
}
|
||||
|
||||
@@ -107,6 +109,8 @@ void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range)
|
||||
{
|
||||
PlaybackCache *cache = static_cast<PlaybackCache*>(sender());
|
||||
|
||||
cache->ClearRequestRange(range);
|
||||
|
||||
AudioInvalidatedFromNode(cache, range);
|
||||
}
|
||||
|
||||
@@ -240,7 +244,10 @@ void PreviewAutoCacher::VideoRendered()
|
||||
void PreviewAutoCacher::ProcessUpdateQueue()
|
||||
{
|
||||
// Iterate everything that happened to the graph and do the same thing on our end
|
||||
foreach (const QueuedJob& job, graph_update_queue_) {
|
||||
while (!graph_update_queue_.empty()) {
|
||||
QueuedJob job = graph_update_queue_.front();
|
||||
graph_update_queue_.pop_front();
|
||||
|
||||
switch (job.type) {
|
||||
case QueuedJob::kNodeAdded:
|
||||
AddNode(job.node);
|
||||
@@ -262,7 +269,6 @@ void PreviewAutoCacher::ProcessUpdateQueue()
|
||||
break;
|
||||
}
|
||||
}
|
||||
graph_update_queue_.clear();
|
||||
|
||||
// Indicate that we have synchronized to this point, which is compared with the graph change
|
||||
// time to see if our copied graph is up to date
|
||||
@@ -368,22 +374,22 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy)
|
||||
void PreviewAutoCacher::ConnectToNodeCache(Node *node)
|
||||
{
|
||||
connect(node->video_frame_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
||||
|
||||
connect(node->thumbnail_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
||||
|
||||
connect(node->audio_playback_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
||||
|
||||
connect(node->waveform_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
||||
|
||||
@@ -396,27 +402,32 @@ void PreviewAutoCacher::ConnectToNodeCache(Node *node)
|
||||
&PlaybackCache::CancelAll,
|
||||
this,
|
||||
&PreviewAutoCacher::CancelForCache);
|
||||
|
||||
node->video_frame_cache()->ResignalRequests();
|
||||
node->thumbnail_cache()->ResignalRequests();
|
||||
node->audio_playback_cache()->ResignalRequests();
|
||||
node->waveform_cache()->ResignalRequests();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::DisconnectFromNodeCache(Node *node)
|
||||
{
|
||||
disconnect(node->video_frame_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
||||
|
||||
disconnect(node->thumbnail_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
||||
|
||||
disconnect(node->audio_playback_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
||||
|
||||
disconnect(node->waveform_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
||||
|
||||
@@ -466,6 +477,8 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR
|
||||
using_tb = viewer_node_->GetVideoParams().frame_rate_as_time_base();
|
||||
}
|
||||
|
||||
cache->ClearRequestRange(range);
|
||||
|
||||
TimeRangeListFrameIterator iterator({range}, using_tb);
|
||||
pending_video_jobs_.push_back({node, cache, range, iterator});
|
||||
video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), graph_changed_time_);
|
||||
@@ -475,6 +488,9 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR
|
||||
void PreviewAutoCacher::StartCachingAudioRange(PlaybackCache *cache, const TimeRange &range)
|
||||
{
|
||||
Node *node = cache->parent();
|
||||
|
||||
cache->ClearRequestRange(range);
|
||||
|
||||
pending_audio_jobs_.push_back({node, cache, range});
|
||||
audio_cache_data_[cache].job_tracker.insert(range, graph_changed_time_);
|
||||
TryRender();
|
||||
@@ -486,6 +502,8 @@ void PreviewAutoCacher::VideoInvalidatedFromNode(PlaybackCache *cache, const Tim
|
||||
// want to dedicate all our rendering power to realtime feedback for the user
|
||||
//CancelVideoTasks(node);
|
||||
|
||||
cache->ClearRequestRange(range);
|
||||
|
||||
// If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames
|
||||
if (!NodeInputDragger::IsInputBeingDragged()) {
|
||||
StartCachingVideoRange(cache, range);
|
||||
@@ -498,6 +516,8 @@ void PreviewAutoCacher::AudioInvalidatedFromNode(PlaybackCache *cache, const Tim
|
||||
// cancelled, so some areas may end up unrendered forever
|
||||
// ClearAudioQueue();
|
||||
|
||||
cache->ClearRequestRange(range);
|
||||
|
||||
// If we're auto-caching audio or require realtime waveforms, we'll have to render this
|
||||
StartCachingAudioRange(cache, range);
|
||||
}
|
||||
@@ -553,37 +573,37 @@ void PreviewAutoCacher::SetRendersPaused(bool e)
|
||||
|
||||
void PreviewAutoCacher::NodeAdded(Node *node)
|
||||
{
|
||||
graph_update_queue_.append({QueuedJob::kNodeAdded, node, NodeInput(), nullptr});
|
||||
graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr});
|
||||
UpdateGraphChangeValue();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::NodeRemoved(Node *node)
|
||||
{
|
||||
graph_update_queue_.append({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr});
|
||||
graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr});
|
||||
UpdateGraphChangeValue();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::EdgeAdded(Node *output, const NodeInput &input)
|
||||
{
|
||||
graph_update_queue_.append({QueuedJob::kEdgeAdded, nullptr, input, output});
|
||||
graph_update_queue_.push_back({QueuedJob::kEdgeAdded, nullptr, input, output});
|
||||
UpdateGraphChangeValue();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::EdgeRemoved(Node *output, const NodeInput &input)
|
||||
{
|
||||
graph_update_queue_.append({QueuedJob::kEdgeRemoved, nullptr, input, output});
|
||||
graph_update_queue_.push_back({QueuedJob::kEdgeRemoved, nullptr, input, output});
|
||||
UpdateGraphChangeValue();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::ValueChanged(const NodeInput &input)
|
||||
{
|
||||
graph_update_queue_.append({QueuedJob::kValueChanged, nullptr, input, nullptr});
|
||||
graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr});
|
||||
UpdateGraphChangeValue();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::ValueHintChanged(const NodeInput &input)
|
||||
{
|
||||
graph_update_queue_.append({QueuedJob::kValueHintChanged, nullptr, input, nullptr});
|
||||
graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, nullptr});
|
||||
UpdateGraphChangeValue();
|
||||
}
|
||||
|
||||
@@ -591,7 +611,7 @@ void PreviewAutoCacher::TryRender()
|
||||
{
|
||||
delayed_requeue_timer_.stop();
|
||||
|
||||
if (!graph_update_queue_.isEmpty()) {
|
||||
if (!graph_update_queue_.empty()) {
|
||||
// Check if we have jobs running in other threads that shouldn't be interrupted right now
|
||||
// NOTE: We don't check for downloads because, while they run in another thread, they don't
|
||||
// require any access to the graph and therefore don't risk race conditions.
|
||||
|
||||
@@ -160,7 +160,7 @@ private:
|
||||
|
||||
Project copied_project_;
|
||||
|
||||
QVector<QueuedJob> graph_update_queue_;
|
||||
std::list<QueuedJob> graph_update_queue_;
|
||||
QHash<Node*, Node*> copy_map_;
|
||||
QHash<NodeGraph*, NodeGraph*> graph_map_;
|
||||
ViewerOutput* copied_viewer_node_;
|
||||
|
||||
@@ -293,7 +293,7 @@ void MenuShared::EnableDisableTriggered()
|
||||
|
||||
void MenuShared::NestTriggered()
|
||||
{
|
||||
qDebug() << "FIXME: Stub";
|
||||
PanelManager::instance()->MostRecentlyFocused<TimelinePanel>()->NestSelectedClips();
|
||||
}
|
||||
|
||||
void MenuShared::DefaultTransitionTriggered()
|
||||
|
||||
@@ -539,12 +539,16 @@ void TimelineWidget::DecreaseTrackHeight()
|
||||
|
||||
void TimelineWidget::InsertFootageAtPlayhead(const QVector<ViewerOutput*>& footage)
|
||||
{
|
||||
import_tool_->PlaceAt(footage, GetTime(), true);
|
||||
auto command = new MultiUndoCommand();
|
||||
import_tool_->PlaceAt(footage, GetTime(), true, command);
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
|
||||
void TimelineWidget::OverwriteFootageAtPlayhead(const QVector<ViewerOutput *> &footage)
|
||||
{
|
||||
import_tool_->PlaceAt(footage, GetTime(), false);
|
||||
auto command = new MultiUndoCommand();
|
||||
import_tool_->PlaceAt(footage, GetTime(), false, command);
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
|
||||
void TimelineWidget::ToggleLinksOnSelected()
|
||||
@@ -788,13 +792,14 @@ void TimelineWidget::RecordingCallback(const QString &filename, const TimeRange
|
||||
task.Start();
|
||||
|
||||
MultiUndoCommand *import_command = task.GetCommand();
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(import_command);
|
||||
|
||||
if (task.GetImportedFootage().empty()) {
|
||||
qCritical() << "Failed to import recorded audio file" << filename;
|
||||
} else {
|
||||
import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, track.index());
|
||||
import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, import_command, track.index());
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(import_command);
|
||||
}
|
||||
|
||||
void TimelineWidget::EnableRecordingOverlay(const TimelineCoordinate &coord)
|
||||
@@ -839,6 +844,95 @@ void TimelineWidget::AddTentativeSubtitleTrack()
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::NestSelectedClips()
|
||||
{
|
||||
if (!GetConnectedNode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<Block*> blocks = this->selected_blocks_;
|
||||
if (blocks.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<Track::Reference> tracks(blocks.size());
|
||||
QVector<TimeRange> times(blocks.size());
|
||||
QVector<int> track_offset(Track::kCount, INT_MAX);
|
||||
rational start_time = RATIONAL_MAX;
|
||||
rational end_time = RATIONAL_MIN;
|
||||
for (int i=0; i<blocks.size(); i++) {
|
||||
Block *b = blocks.at(i);
|
||||
|
||||
Track::Reference tf = b->track()->ToReference();;
|
||||
tracks[i] = tf;
|
||||
times[i] = b->range();
|
||||
|
||||
int &to = track_offset[tf.type()];
|
||||
to = std::min(to, tf.index());
|
||||
|
||||
start_time = std::min(start_time, b->in());
|
||||
end_time = std::max(end_time, b->out());
|
||||
}
|
||||
|
||||
auto move_to_nest_command = new MultiUndoCommand();
|
||||
|
||||
// Remove blocks from this sequence
|
||||
ReplaceBlocksWithGaps(blocks, false, move_to_nest_command);
|
||||
|
||||
// Create new sequence
|
||||
Project *project = this->GetConnectedNode()->project();
|
||||
Sequence *nest = Core::CreateNewSequenceForProject(tr("Nested Sequence %1"), project);
|
||||
nest->SetVideoParams(GetConnectedNode()->GetVideoParams());
|
||||
nest->SetAudioParams(GetConnectedNode()->GetAudioParams());
|
||||
move_to_nest_command->add_child(new NodeAddCommand(project, nest));
|
||||
|
||||
// Add to same folder
|
||||
move_to_nest_command->add_child(new FolderAddChild(this->GetConnectedNode()->folder(), nest));
|
||||
|
||||
// Place blocks in new sequence
|
||||
for (int i=0; i<blocks.size(); i++) {
|
||||
Block *b = blocks.at(i);
|
||||
|
||||
const TimeRange &range = times.at(i);
|
||||
Track::Reference track = tracks.at(i);
|
||||
|
||||
move_to_nest_command->add_child(new TrackPlaceBlockCommand(nest->track_list(track.type()),
|
||||
track.index() - track_offset.at(track.type()),
|
||||
b, range.in() - start_time));
|
||||
}
|
||||
|
||||
// Do this command now, because we later do checks and actions that rely on these having been done
|
||||
move_to_nest_command->redo_now();
|
||||
|
||||
auto meta_command = new MultiUndoCommand();
|
||||
meta_command->add_child(move_to_nest_command);
|
||||
|
||||
// Find first free track index
|
||||
bool empty = false;
|
||||
int index = -1;
|
||||
while (!empty) {
|
||||
index++;
|
||||
empty = true;
|
||||
for (int i=0; i<Track::kCount; i++) {
|
||||
if (track_offset.at(i) == INT_MAX) {
|
||||
// No clips on this track
|
||||
continue;
|
||||
}
|
||||
|
||||
TrackList *list = sequence()->track_list(static_cast<Track::Type>(i));
|
||||
if (index < list->GetTrackCount() && !list->GetTrackAt(index)->IsRangeFree(TimeRange(start_time, end_time))) {
|
||||
empty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Place new sequence in this sequence
|
||||
import_tool_->PlaceAt({nest}, start_time, false, meta_command, index);
|
||||
|
||||
Core::instance()->undo_stack()->push(meta_command);
|
||||
}
|
||||
|
||||
void TimelineWidget::ClearTentativeSubtitleTrack()
|
||||
{
|
||||
if (subtitle_show_command_) {
|
||||
|
||||
@@ -112,6 +112,8 @@ public:
|
||||
|
||||
void AddTentativeSubtitleTrack();
|
||||
|
||||
void NestSelectedClips();
|
||||
|
||||
/**
|
||||
* @brief Timelines should always be connected to sequences
|
||||
*/
|
||||
|
||||
@@ -183,7 +183,9 @@ void ImportTool::DragLeave(QDragLeaveEvent* event)
|
||||
void ImportTool::DragDrop(TimelineViewMouseEvent *event)
|
||||
{
|
||||
if (!dragged_footage_.isEmpty()) {
|
||||
DropGhosts(event->GetModifiers() & Qt::ControlModifier);
|
||||
auto command = new MultiUndoCommand();
|
||||
DropGhosts(event->GetModifiers() & Qt::ControlModifier, command);
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
|
||||
event->accept();
|
||||
} else {
|
||||
@@ -191,7 +193,7 @@ void ImportTool::DragDrop(TimelineViewMouseEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational &start, bool insert, int track_offset)
|
||||
void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational &start, bool insert, MultiUndoCommand *command, int track_offset)
|
||||
{
|
||||
DraggedFootageData refs;
|
||||
|
||||
@@ -199,10 +201,10 @@ void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational
|
||||
refs.append({f, f->GetEnabledStreamsAsReferences()});
|
||||
}
|
||||
|
||||
PlaceAt(refs, start, insert, track_offset);
|
||||
PlaceAt(refs, start, insert, command, track_offset);
|
||||
}
|
||||
|
||||
void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, int track_offset)
|
||||
void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, MultiUndoCommand *command, int track_offset)
|
||||
{
|
||||
dragged_footage_ = footage;
|
||||
|
||||
@@ -211,7 +213,7 @@ void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &star
|
||||
}
|
||||
|
||||
PrepGhosts(start, track_offset);
|
||||
DropGhosts(insert);
|
||||
DropGhosts(insert, command);
|
||||
}
|
||||
|
||||
void ImportTool::FootageToGhosts(rational ghost_start, const DraggedFootageData &sorted, const rational& dest_tb, const int& track_start)
|
||||
@@ -292,9 +294,9 @@ void ImportTool::PrepGhosts(const rational& frame, const int& track_index)
|
||||
}
|
||||
}
|
||||
|
||||
void ImportTool::DropGhosts(bool insert)
|
||||
void ImportTool::DropGhosts(bool insert, MultiUndoCommand *parent_command)
|
||||
{
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
auto command = new MultiUndoCommand();
|
||||
|
||||
if (MultiUndoCommand *c = parent()->TakeSubtitleSectionCommand()) {
|
||||
command->add_child(c);
|
||||
@@ -500,7 +502,10 @@ void ImportTool::DropGhosts(bool insert)
|
||||
command->add_child(new OpenSequenceCommand(sequence));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
// Do command now because RequestInvalidatedFromConnected relies on track type, which will be
|
||||
// "none" before this command is done because it won't be connected to any track
|
||||
command->redo_now();
|
||||
parent_command->add_child(command);
|
||||
|
||||
while (!imported_clips.empty()) {
|
||||
imported_clips.front()->RequestInvalidatedFromConnected();
|
||||
|
||||
@@ -37,8 +37,8 @@ public:
|
||||
|
||||
using DraggedFootageData = QVector<QPair<ViewerOutput*, QVector<Track::Reference> > >;
|
||||
|
||||
void PlaceAt(const QVector<ViewerOutput *> &footage, const rational& start, bool insert, int track_offset = 0);
|
||||
void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, int track_offset = 0);
|
||||
void PlaceAt(const QVector<ViewerOutput *> &footage, const rational& start, bool insert, MultiUndoCommand *command, int track_offset = 0);
|
||||
void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, MultiUndoCommand *command, int track_offset = 0);
|
||||
|
||||
enum DropWithoutSequenceBehavior {
|
||||
kDWSAsk,
|
||||
@@ -52,7 +52,7 @@ private:
|
||||
|
||||
void PrepGhosts(const rational &frame, const int &track_index);
|
||||
|
||||
void DropGhosts(bool insert);
|
||||
void DropGhosts(bool insert, MultiUndoCommand *parent_command);
|
||||
|
||||
TimelineViewGhostItem* CreateGhost(const TimeRange &range, const rational &media_in, const Track::Reference &track);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user